创建嵌套迭代器的迭代器



如何将以下代码打包到单个迭代器中?

use std::io::{BufRead, BufReader};
use std::fs::File;
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));
for line in file.lines() {
for ch in line.expect("Unable to read line").chars() {
println!("Character: {}", ch);
}
}

天真地,我想有一些像(我跳过unwraps)

let lines = file.lines().next();
Reader {
line: lines,
char: next().chars()
}

并迭代Reader.char直到到达None,然后将Reader.line刷新到新行,将Reader.char刷新到该行的第一个字符。但这似乎是不可能的,因为Reader.char依赖于临时变量。

请注意这个问题是关于嵌套迭代器的,以读取文本文件为例。

您可以使用flat_map()迭代器实用程序创建新的迭代器,该迭代器可以为调用它的迭代器中的每个项生成任意数量的项。

在本例中,由于lines()返回Results的迭代器,因此必须处理Err的情况,这使情况变得复杂。

还存在.chars()引用原始字符串以避免额外分配的问题,因此您必须将字符收集到另一个可迭代容器中。

解决这两个问题导致了这个混乱:

fn example() -> impl Iterator<Item=Result<char, std::io::Error>> {
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));

file.lines().flat_map(|line| match line {
Err(e) => vec![Err(e)],
Ok(line) => line.chars().map(Ok).collect(),
})
}

如果String给了我们一个into_chars()方法,我们可以在这里避免collect(),但这样我们就会有不同类型的迭代器,需要使用Box<dyn Iterator>either::Either之类的方法。

由于这里已经使用了.expect(),您可以通过在闭包中使用.expect()来简化一点,以避免处理Err的情况:

fn example() -> impl Iterator<Item=char> {
let file = BufReader::new(File::open("sample.txt").expect("Unable to open file"));

file.lines().flat_map(|line|
line.expect("Unable to read line").chars().collect::<Vec<_>>()
)
}

在一般情况下,flat_map()通常很容易。你只需要注意你是在迭代自己的值还是借来的值;这两种情况都有一些尖锐的角落。在这种情况下,迭代拥有的String值使得使用.chars()出现问题。如果可以遍历借来的str片,就不必遍历.collect()了。

根据@cdhowie的答案和这个建议使用IntoIter来获得拥有字符的迭代器的答案,我能够提出这个最接近我期望的解决方案:

use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, Lines};
use std::vec::IntoIter;
struct Reader {
lines: Lines<BufReader<File>>,
iter: IntoIter<char>,
}
impl Reader {
fn new(filename: &str) -> Self {
let file = BufReader::new(File::open(filename).expect("Unable to open file"));
let mut lines = file.lines();
let iter = Reader::char_iter(lines.next().expect("Unable to read file"));
Reader { lines, iter }
}
fn char_iter(line: io::Result<String>) -> IntoIter<char> {
line.unwrap().chars().collect::<Vec<_>>().into_iter()
}
}
impl Iterator for Reader {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
None => {
self.iter = match self.lines.next() {
None => return None,
Some(line) => Reader::char_iter(line),
};
Some('n')
}
Some(val) => Some(val),
}
}
}

按预期运行:

let reader = Reader::new("src/main.rs");
for ch in reader {
print!("{}", ch);
}

最新更新