rust 生成嵌套迭代器的迭代器

k7fdbhmy  于 2023-01-17  发布在  其他
关注(0)|答案(1)|浏览(159)

我如何将下面的代码打包到一个迭代器中?

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);
   }
}

天真地说,我希望有这样的东西(我跳过了解开)

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

并迭代Reader.char,直到None,然后刷新Reader.line到新的一行,刷新Reader.char到该行的第一个字符,但这似乎是不可能的,因为Reader.char依赖于临时变量。
请注意,这个问题是关于嵌套迭代器的,阅读文本文件只是一个例子。

iszxjhcz

iszxjhcz1#

可以使用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()

相关问题