了解收集 `flat_map` 与收集 rust 中的 `map` 之间的细微差别

Posted

技术标签:

【中文标题】了解收集 `flat_map` 与收集 rust 中的 `map` 之间的细微差别【英文标题】:Understanding the nuances between collecting `flat_map` vs collecting `map` in rust 【发布时间】:2022-01-11 18:14:33 【问题描述】:

我正在使用 Advent of Code 学习 Rust。 要解析 2021 年第 4 天,我需要解析以下字符串:

88 67 20 19 15
22 76 86 44 73
 7 42  6 69 25
12 68 92 21 75
97 45 13 52 70

75 98 24 18 77
17 93 46 49 13
92 56 97 57 66
44  0 65 54 74
23  6 53 42 20

92 94  9 27 41
73 28 62 90 40
78  3 12 37 32
 8 86 91 16 30
84 38 68 11 19

理想情况下,我想生成嵌套向量Vec<Vec<&str>>。 我的问题是我不明白为什么下面的代码不起作用。

let bingo_sheet = grids_str
  split("\r\n\r\n")
  .map(|grid| grid.split_whitespace())
  .collect::<Vec<Vec<&str>>>();

我也不明白货物检查背后的含义。 (好吧,这个特征不存在。但为什么呢?)

error[E0277]: a value of type `Vec<Vec<&str>>` cannot be built from an iterator over elements of type `SplitWhitespace<'_>`
  --> src\main.rs:36:10
   |
36 |         .collect::<Vec<Vec<&str>>>();
   |          ^^^^^^^ value of type `Vec<Vec<&str>>` cannot be built from `std::iter::Iterator<Item=SplitWhitespace<'_>>`
   |
   = help: the trait `FromIterator<SplitWhitespace<'_>>` is not implemented for `Vec<Vec<&str>>`

经过一些实验,flat_map 的用法似乎可行。

let bingo_sheet = grids_str
   .split("\r\n\r\n")
   .flat_map(|grid| grid.split_whitespace())
   .collect::<Vec<&str>>();

我不明白这里发生了什么。flat_map 代码的行为符合预期,但 map 代码不符合预期。

【问题讨论】:

.map(|grid| grid.split_whitespace().collect()) 可能有效,.map(|grid| grid.split_whitespace().collect::&lt;Vec&lt;&amp;str&gt;&gt;()) 应该有效。 这不是“过早的”——你在外部map 中收集内部Vec,然后收集外部Vec。你需要(至少)两个collects,如果你想收集两个(层)Vecs。 @gberth 因为FromIterator 是为Vec&lt;T&gt; 实现的,所以你需要先收集它。 flat_map 版本有效,因为您只得到一个 Vec 我建议将每个板表示为一维向量。整体上使用起来更容易,效率也更高。唯一的缺点是,对于索引,您不能使用board[y][x],而需要使用board[5 * y + x]。在我看来,这个缺点真的很小,特别是对于这个问题 - 你真的不需要通过 xy 进行索引。 一个 5x5 板的Vec&lt;Vec&lt;u8&gt;&gt; 有 6 个堆分配和一些外部向量的堆栈存储空间。每个元素访问都需要两个指针间接——首先,跟随堆栈的外部点到包含指向各个行的指针的堆分配,然后跟随指向右行的指针。 [[u8; 5]; 5] 的内存布局在堆栈上是连续的,因此它的指针间接为零并且有可能成为最快的方法之一,但使用起来不是很方便,因为你不能轻易地收集到它。 【参考方案1】:

在我写这篇文章时,cmet 中已经简要回答了这个问题,但也许更详细的版本仍然有帮助!

我也不明白货物检查背后的含义。 (好吧,这个特征不存在。但为什么呢?)

std 提供了经常使用的转换,但它不会为您完成所有转换。

split_whitespace 的调用会返回一个SplitWhitespace 结构,该结构实现(在其余部分我将简单地说“是”)Iterator。 对map 的调用返回一个Map 结构,它是对其给定函数的返回类型的迭代器,因此它是对字符串切片的迭代器的迭代器。

因为Map 是一个迭代器,所以它有一个collect 方法。正如您在文档中看到的那样,如果存在实现FromIterator&lt;Self::Item&gt;B,则此方法存在。这个B 是你的Vec&lt;Vec&lt;&amp;str&gt;&gt;Self::ItemMap 的项目SplitWhiteSpace 结构。所以collect 在问,“Vec&lt;Vec&lt;&amp;str&gt;&gt; 是否实现了FromIterator&lt;SplitWhiteSpace&gt;?如果是,我存在,如果不是,我拒绝存在”。

(FromIterator&lt;SplitWhiteSpace&gt; 并不意味着Vec&lt;T&gt; 可以由SplitWhiteSpace 结构体构成,而是它可以由某种类型I 构成,即(或可以构成)项目@ 的迭代器987654347@.)

让我们查看 Vec 文档,看看 Vec&lt;Vec&lt;&amp;str&gt;&gt; 是否实现了 FromIterator&lt;SplitWhiteSpace&gt;Vec does implement FromIterator&lt;T&gt; but only for Vec&lt;T&gt;,因此在您的情况下,它实现了FromIterator&lt;Vec&lt;&amp;str&gt;&gt;,但collect 要求它实现FromIterator&lt;SplitWhiteSpace&gt;。不是这样,您的代码就无法编译。

换句话说,当将Map&lt;X&gt; 收集到Vec&lt;Y&gt; 中时,X 必须等于 Y。

这就是为什么在.map 中添加collect() 有效。因为这使它成为Map&lt;Vec&lt;&amp;str&gt;&gt;,这意味着类型匹配。 flatmap 也有效,因为这会创建一个 FlatMap&lt;&amp;str&gt;,其工作方式类似于 Map,并且可以收集到 Vec&lt;&amp;str&gt; 中。 (flat_map 在其给定函数中不需要 collect,因为它可以处理其中的迭代器)

【讨论】:

谢谢,@Paul 这很有用。

以上是关于了解收集 `flat_map` 与收集 rust 中的 `map` 之间的细微差别的主要内容,如果未能解决你的问题,请参考以下文章

Rust语言——无虚拟机无垃圾收集器无运行时无空指针/野指针/内存越界/缓冲区溢出/段错误无数据竞争

垃圾收集算法与垃圾收集器

垃圾收集器与内存分配策略之篇一:简要概述和垃圾收集算法

Rust为什么我建议你学一下 Rust | Rust 初探

Rust为什么我建议你学一下 Rust | Rust 初探

深入了解JVM——垃圾收集器与内存分配策略