如何将以下代码打包到单个迭代器中?
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()
返回Result
s的迭代器,因此必须处理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);
}