为什么在使用 `flat_map` 时需要收集到一个向量中?
Why do I need to collect into a vector when using `flat_map`?
我正在 Project Euler 96 to teach myself Rust. I've written this code to read in the file and convert it into a vector of integers (Playground).
let file = File::open(&args[1]).expect("Sudoku file not found");
let reader = BufReader::new(file);
let x = reader
.lines()
.map(|x| x.unwrap())
.filter(|x| !x.starts_with("Grid"))
.flat_map(|s| s.chars().collect::<Vec<_>>()) // <-- collect here!
.map(|x| x.to_digit(10).unwrap())
.collect::<Vec<_>>();
这一切都很好,但我很困惑为什么我必须在我的 flat_map
中收集到一个矢量(我假设创建不需要的矢量会立即被销毁是低效的)。如果我不收集,它就不会编译:
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:13:23
|
13 | .flat_map(|s| s.chars())
| -^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `s` is borrowed here
来自 the docs 的示例显示几乎相同的代码,但不需要收集:
let words = ["alpha", "beta", "gamma"];
// chars() returns an iterator
let merged: String = words.iter()
.flat_map(|s| s.chars())
.collect();
assert_eq!(merged, "alphabetagamma");
为什么我的代码不一样?
迭代器 reader.lines().map(|x| x.unwrap())
迭代 String
项,即按值。因此,在 .flat_map(|s| ...)
中,变量 s
的类型为 String
(即拥有,而非借用)。换句话说:字符串现在是一个局部变量,存在于函数中。这是一个简单的规则,您不能 return 引用局部变量(参见 this Q&A)。但这正是 s.chars()
所做的,即使它有点隐藏。
正在看at str::chars
:
pub fn chars(&self) -> Chars<'_>
可以看出字符串是借用的。 returned Chars
对象包含对原始字符串的引用。这就是为什么我们不能从闭包中 return s.chars()
。
So why is it different in my code?
在文档的示例中,迭代器 words.iter()
实际上迭代 &&'static str
类型的项目。调用 s.chars()
也会 return 借用一些字符串的 Chars
对象,但是该字符串的生命周期是 'static
(永远存在),所以 [=59= 没有问题]ing Chars
从闭包中。
解决方案?
如果标准库有一个使用 String
的 OwnedChars
迭代器,那就太好了,就像 Chars
一样工作,并在迭代器被删除后删除字符串。在这种情况下,调用 s.owned_chars()
没问题,因为 returned 对象不引用本地 s
,但拥有它。但是:标准库中不存在这样的自有迭代器!
I'm assuming creating unneeded vectors which will be immediately destroyed is inefficient
是的,这在某种程度上是正确的。但是您可能错过了 reader.lines()
迭代器还会创建 String
类型的临时对象。那些或多或少也立即被摧毁了!因此,即使 flat_map
中没有 collect
,您也会有一堆不必要的分配。请注意,有时这没关系。在这种情况下,我猜想与您必须实现的实际算法相比,输入解析速度非常快。那么...只是collect
?在这种情况下可能没问题。
如果你想要高性能的输入解析,我认为你将无法避免标准循环,特别是为了避免不必要的 String
分配。 (Playground)
let mut line = String::new();
let mut input = Vec::new();
loop {
line.clear(); // clear contents, but keep memory buffer
// TODO: handle IO error properly
let bytes_read = reader.read_line(&mut line).expect("IO error");
if bytes_read == 0 {
break;
}
if line.starts_with("Grid") {
continue;
}
// TODO: handle invalid input error
input.extend(line.trim().chars().map(|c| c.to_digit(10).unwrap()));
}
除了另一个答案,请注意自有迭代器很容易编写:
struct OwnedChars {
s: String,
i: usize,
}
impl Iterator for OwnedChars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let c = self.s[self.i..].chars().next()?;
self.i += c.len_utf8();
Some(c)
}
}
fn into_iter(string: String) -> OwnedChars {
OwnedChars {
s: string,
i: 0,
}
}
fn main() {
let owned_iter = into_iter("Zażółć gęślą jaźń".into());
for c in owned_iter {
println!("{}", c);
}
}
然后,你不需要收集:
fn main() {
use std::io::prelude::*;
let file = std::fs::File::open(std::env::args().nth(1).unwrap()).expect("Sudoku file not found");
let reader = std::io::BufReader::new(file);
let x = reader
.lines()
.map(|x| x.unwrap())
.filter(|x| !x.starts_with("Grid"))
.flat_map(into_iter)
.map(|x| x.to_digit(10).unwrap())
.collect::<Vec<_>>();
}
我正在 Project Euler 96 to teach myself Rust. I've written this code to read in the file and convert it into a vector of integers (Playground).
let file = File::open(&args[1]).expect("Sudoku file not found");
let reader = BufReader::new(file);
let x = reader
.lines()
.map(|x| x.unwrap())
.filter(|x| !x.starts_with("Grid"))
.flat_map(|s| s.chars().collect::<Vec<_>>()) // <-- collect here!
.map(|x| x.to_digit(10).unwrap())
.collect::<Vec<_>>();
这一切都很好,但我很困惑为什么我必须在我的 flat_map
中收集到一个矢量(我假设创建不需要的矢量会立即被销毁是低效的)。如果我不收集,它就不会编译:
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:13:23
|
13 | .flat_map(|s| s.chars())
| -^^^^^^^^
| |
| returns a value referencing data owned by the current function
| `s` is borrowed here
来自 the docs 的示例显示几乎相同的代码,但不需要收集:
let words = ["alpha", "beta", "gamma"];
// chars() returns an iterator
let merged: String = words.iter()
.flat_map(|s| s.chars())
.collect();
assert_eq!(merged, "alphabetagamma");
为什么我的代码不一样?
迭代器 reader.lines().map(|x| x.unwrap())
迭代 String
项,即按值。因此,在 .flat_map(|s| ...)
中,变量 s
的类型为 String
(即拥有,而非借用)。换句话说:字符串现在是一个局部变量,存在于函数中。这是一个简单的规则,您不能 return 引用局部变量(参见 this Q&A)。但这正是 s.chars()
所做的,即使它有点隐藏。
正在看at str::chars
:
pub fn chars(&self) -> Chars<'_>
可以看出字符串是借用的。 returned Chars
对象包含对原始字符串的引用。这就是为什么我们不能从闭包中 return s.chars()
。
So why is it different in my code?
在文档的示例中,迭代器 words.iter()
实际上迭代 &&'static str
类型的项目。调用 s.chars()
也会 return 借用一些字符串的 Chars
对象,但是该字符串的生命周期是 'static
(永远存在),所以 [=59= 没有问题]ing Chars
从闭包中。
解决方案?
如果标准库有一个使用 String
的 OwnedChars
迭代器,那就太好了,就像 Chars
一样工作,并在迭代器被删除后删除字符串。在这种情况下,调用 s.owned_chars()
没问题,因为 returned 对象不引用本地 s
,但拥有它。但是:标准库中不存在这样的自有迭代器!
I'm assuming creating unneeded vectors which will be immediately destroyed is inefficient
是的,这在某种程度上是正确的。但是您可能错过了 reader.lines()
迭代器还会创建 String
类型的临时对象。那些或多或少也立即被摧毁了!因此,即使 flat_map
中没有 collect
,您也会有一堆不必要的分配。请注意,有时这没关系。在这种情况下,我猜想与您必须实现的实际算法相比,输入解析速度非常快。那么...只是collect
?在这种情况下可能没问题。
如果你想要高性能的输入解析,我认为你将无法避免标准循环,特别是为了避免不必要的 String
分配。 (Playground)
let mut line = String::new();
let mut input = Vec::new();
loop {
line.clear(); // clear contents, but keep memory buffer
// TODO: handle IO error properly
let bytes_read = reader.read_line(&mut line).expect("IO error");
if bytes_read == 0 {
break;
}
if line.starts_with("Grid") {
continue;
}
// TODO: handle invalid input error
input.extend(line.trim().chars().map(|c| c.to_digit(10).unwrap()));
}
除了另一个答案,请注意自有迭代器很容易编写:
struct OwnedChars {
s: String,
i: usize,
}
impl Iterator for OwnedChars {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let c = self.s[self.i..].chars().next()?;
self.i += c.len_utf8();
Some(c)
}
}
fn into_iter(string: String) -> OwnedChars {
OwnedChars {
s: string,
i: 0,
}
}
fn main() {
let owned_iter = into_iter("Zażółć gęślą jaźń".into());
for c in owned_iter {
println!("{}", c);
}
}
然后,你不需要收集:
fn main() {
use std::io::prelude::*;
let file = std::fs::File::open(std::env::args().nth(1).unwrap()).expect("Sudoku file not found");
let reader = std::io::BufReader::new(file);
let x = reader
.lines()
.map(|x| x.unwrap())
.filter(|x| !x.starts_with("Grid"))
.flat_map(into_iter)
.map(|x| x.to_digit(10).unwrap())
.collect::<Vec<_>>();
}