• Rust中filter_map 和 flat_map的用法
  • 发布于 2个月前
  • 1251 热度
    0 评论
如果适配器的每个输入项都只产生一个输出项,使用 map 是没问题的。但是,如果想从迭代中删除某些项,或者用零或多个其它项来替换某项,该怎么办呢?filter_map 和 flat_map 适配器应运而生。

filter_map 适配器与 map 类似,只是它除了可以通过闭包函数将某项转换为新项(类同 map 的功能)外,还可以将项从迭代中删除。因此,它有点像 filter 和 map 的结合体。其签名如下:
fn filter_map<B, F>(self, f: F) -> impl Iterator<Item=B> 
    where Self: Sized, F: FnMut(Self::Item) -> Option<B>;
与 map 的签名基本相同,只是此处闭包返回的是 Option<B> 类型,而不是简单的 B。当闭包返回 None 时,迭代中的项将被删除;而当闭包返回 Some(b) 时,b 便是 filter_map 产生的下一项。

例如,假设要扫描一个字符串,查找用空白分隔的、可以解析为数字的单词,然后处理其中的数字,去掉其他词。可以这样写:
use std::str::FromStr;

let text = "1\nfrond .25 289\n3.1415 estuary\n";
for number in text
        .split_whitespace()
        .filter_map(|w| f64::from_str(w).ok()) {
    println!("{:4.2}", number.sqrt());
}
示例打印结果:
1.00
0.50
17.00
1.77
(译者注:该示例打印了 number.sqrt() 的结果,反而让例子看起来很别扭。number 的输出结果为:1.00、0.25、289.00、3.14)

传入 filter_map 的闭包尝试用 f64::from_str 解析每个以空白分隔的单词,然后返回 一个 Result<f64,ParseFloatError>,.ok() 再将其转化为一个 Option<f64>:解析失败的结果为 None,而解析成功的结果为 Some(v)。filter_map 迭代器会删除所有 None 值,并为每个 Some(v) 生成值 v。

但是,像这样把 map 和 filter 融合到一个操作中,而不是直接使用这些适配器,又有什么意义呢?filter_map 适配程序在类似刚才的情况下显示出了其价值,在这种情况下,决定是否将某个项包含在迭代中的最佳方法就是尝试实际去处理它。只用 filter 和 map 也能做同样的事情,但有点不够优雅:
text.split_whitespace()
    .map(|w| f64::from_str(w))
    .filter(|r| r.is_ok())
    .map(|r| r.unwrap())
flat_map 与 map、filter_map 一脉相承,只不过它的闭包返回的不像 map 那样只是一个项,也不像 filter_map 那样是零或一个项,而是由任意多个项组成的序列。flat_map迭代器则将闭包返回的这些项串联起来后返回。

其签名如下:
fn flat_map<U, F>(self, f: F) -> impl Iterator<Item=U::Item>
    where F: FnMut(Self::Item) -> U, U: IntoIterator;
传递给 flat_map 的闭包必须返回一个可迭代类型(实现了 IntoIterator 的类型),任何可迭代类型都可以。假如我们有一个将国家映射到其主要城市的表格。给定一个国家列表后,该如何遍历这些国家的主要城市呢?
// 堆代码 duidaima.com
use std::collections::HashMap;

let mut major_cities = HashMap::new();
major_cities.insert("Japan", vec!["Tokyo", "Kyoto"]);
major_cities.insert("The United States", vec!["Portland", "Nashville"]);
major_cities.insert("Brazil", vec!["São Paulo", "Brasília"]);
major_cities.insert("Kenya", vec!["Nairobi", "Mombasa"]);
major_cities.insert("The Netherlands", vec!["Amsterdam", "Utrecht"]);

let countries = ["Japan", "Brazil", "Kenya"];
for &city in countries.iter().flat_map(|country| &major_cities[country]) {
    println!("{}", city);
}
代码输出结果:
Tokyo
Kyoto
São Paulo
Brasília
Nairobi
Mombasa
可以这样理解:针对每个国家检索其城市的 vector,将所有 vector 连接成一个序列,然后打印出来。

但请记住,迭代器是惰性的:只有 for 循环调用 flat_map 迭代器的 next 方法才会导致工作的完成,内存中永远不会构建完整的连接序列。取而代之的是一个小的状态机,它从城市迭代器中每次抽取一个项,直到用完为止,然后才为下一个国家生成新的城市迭代器。其效果就像嵌套循环,只不过被包装成了一个迭代器。

用户评论