• Rust中flatten适配器的用法
  • 发布于 2个月前
  • 237 热度
    0 评论
  • 阳光
  • 1 粉丝 29 篇博客
  •   
flatten 适配器会连接一个迭代器的各项,并假设每个项本身都是可迭代的:
use std::collections::BTreeMap;
//  堆代码 duidaima.com
// 城市与公园的映射表:每个值都是个 vector
let mut parks = BTreeMap::new();
parks.insert("Portland", vec!["Mt. Tabor Park", "Forest Park"]);
parks.insert("Kyoto", vec!["Tadasu-no-Mori Forest", "Maruyama Koen"]);
parks.insert("Nashville", vec!["Percy Warner Park", "Dragon Park"]);

// 建立一个包含所有公园的 vector。`values` 返回一个产生
// vector 的迭代器,然后 `flatten` 依次返回每个 vector 的元素。
let all_parks: Vec<_> = parks.values().flatten().cloned().collect();

assert_eq!(all_parks,
vec!["Tadasu-no-Mori Forest", "Maruyama Koen", "Percy Warner Park",
"Dragon Park", "Mt. Tabor Park", "Forest Park"]);
"flatten" 这个名称的意思是将两层结构扁平化为一层结构:BTreeMap 中所有放置公园名称的 vec 被扁平化为能产生所有名称的迭代器。

flatten 的签名如下:
fn flatten(self) -> impl Iterator<Item=Self::Item::Item>
    where Self::Item: IntoIterator;
换句话说,底层迭代器的项本身必须实现 IntoIterator,它实际上就是可迭代的。然后,flatten 方法会串起各个项并返回串联后各项的迭代器。当然,这个过程是惰性的,只有迭代完上一个迭代项时,才会从 self中得出一个新的迭代项。

flatten 方法有几种出人意料的用法。假设有个 Vec<Option<...>>,现在想遍历 其中的 Some 值,那么 flatten 方法就能很好地搞定这个问题:
assert_eq!(vec![None, Some("day"), None, Some("one")]
    .into_iter()
    .flatten()
    .collect::<Vec<_>>(),
    vec!["day", "one"]);
这是因为 Option 本身实现了 IntoIterator,它代表着一个由 0 或 1 个元素组成的序列。None 元素为迭代贡献了 0 个值,而每个 Some 元素都贡献 1 个值。同样,你也可以使用 flatten 对 Option<Vec<...>> 的值进行迭代: None 的行为与空 vector 相同。

Result 也实现了 IntoIterator,其中 Err 代表空序列。因此对返回 Result 值的迭代器使用 flatten 的话,可以有效排除其中所有 Err,从而得到一个未封装的只含成功值的流。我们并不建议在代码中忽略错误,但在足够了解情况时这确实是一种巧妙技巧。

你可能会发现,当真正需要的是 flat_map 时,你却在使用 flatten。例如,标准库中的 str::to_uppercase方法可以将字符串转换为大写字母,它的工作原理是这样的:
fn to_uppercase(&self) -> String {
    self.chars()
        .map(char::to_uppercase)
        .flatten() // 此处有更好的方法
        .collect()
}
之所以需要 flatten 是因为 ch.to_uppercase() 返回的不是单个字符,而是返回了能够产生一个或多个字符的迭代器。将每个字符映射到大写的过程会产生一个由字符迭代器组成的迭代器,而 flatten 会将这些字符迭代器拼接起来,最终 collect 为 String。

这种 map 和 flaten 的组合非常常见,因此 Iterator 提供了 flat_map 适配器来处理这种情况(其实 flat_map 比 flatten 更早加入标准库):
fn to_uppercase(&self) -> String {
    self.chars()
        .flat_map(char::to_uppercase)
        .collect()
}

用户评论