从行迭代器创建单词迭代器

Creating word iterator from line iterator

我有一个字符串迭代器lines,我从标准输入

use std::io::{self, BufRead};

let mut stdin = io::stdin();
let lines = stdin.lock().lines().map(|l| l.unwrap());

lines 迭代器产生 String 类型的值,而不是 &str。我想创建一个迭代器来迭代输入的单词而不是行。看起来这应该是可行的,但我天真的尝试不起作用:

let words = lines.flat_map(|l| l.split_whitespace());

编译器告诉我 l 在被借用的同时被删除,这是有道理的:

error[E0597]: `l` does not live long enough
 --> src/lib.rs:6:36
  |
6 |     let words = lines.flat_map(|l| l.split_whitespace());
  |                                    ^                  - `l` dropped here while still borrowed
  |                                    |
  |                                    borrowed value does not live long enough
7 | }
  | - borrowed value needs to live until here

是否有其他干净的方法可以完成此操作?

在您的示例代码中,lines 是对您从 stdin 获得的 reader 中读入的行的迭代器。正如你所说,它 returns String 个实例,但你没有将它们存储在任何地方。

std::string::String::split_whitespace 定义如下:

pub fn split_whitespace(&self) -> SplitWhitespace

因此,它引用了一个字符串——它不使用该字符串。它 returns 一个产生字符串切片的迭代器 &str - 它引用字符串的一部分,但不拥有它。

事实上,一旦你传递给 flat_map 的闭包用完了,就没有人拥有它,所以它被丢弃了。这将使 words 产生的 &str 悬而未决,因此是错误。

一种解决方案是将线收集到向量中,如下所示:

let lines: Vec<String> = stdin.lock().lines().map(|l| l.unwrap()).collect();

let words = lines.iter().flat_map(|l| l.split_whitespace());

String 个实例保存在 Vec<String> 中,它可以继续存在,以便 words 产生的 &str 有参考价值。

如果有很多行,并且您不想将它们全部保存在内存中,您可能更愿意一次一行地执行:

let lines = stdin.lock().lines().map(|l| l.unwrap());

let words = lines.flat_map(|l| {
    l.split_whitespace()
        .map(|s| s.to_owned())
        .collect::<Vec<String>>()
        .into_iter()
});

这里把每一行的词汇集成一个Vec,一次一行。权衡是减少整体内存消耗,而不是为每行构建 Vec<String> 并将每个单词复制到其中的开销。

您可能一直希望零拷贝实现,它消耗了 lines 产生的 Strings。我认为可以通过创建一个 split_whitespace() 函数来创建一个拥有 String 和 returns 拥有字符串的迭代器。