组合子
迭代器能做的远不止是 for
循环!
如果你查阅 Iterator
特性的文档,你会发现大量的方法集合,可以用来以各种方式转换、过滤和组合迭代器。
这里列举一些最常见的:
map
对迭代器中的每个元素应用一个函数。filter
只保留满足谓词的元素。filter_map
结合了filter
和map
的功能,一步完成过滤和映射。cloned
将引用迭代器转换为值迭代器,并克隆每个元素。enumerate
返回一个新的迭代器,产生(索引, 值)
对。skip
跳过迭代器的前n
个元素。take
在处理完n
个元素后停止迭代器。chain
将两个迭代器合并为一个。
这些方法被称为 组合子。
它们通常被 链式调用 来以简洁且易于阅读的方式创建复杂的转换:
#![allow(unused)] fn main() { let numbers = vec![1, 2, 3, 4, 5]; // 偶数的平方和 let outcome: u32 = numbers.iter() .filter(|&n| n % 2 == 0) .map(|&n| n * n) .sum(); }
闭包
上述 filter
和 map
方法是怎么回事呢?
它们接受 闭包 作为参数。
闭包是 匿名函数,即不由我们熟悉的 fn
语法定义的函数。
它们使用 |args| body
语法定义,其中 args
是参数,body
是函数体。body
可以是一段代码块或单个表达式。
例如:
#![allow(unused)] fn main() { // 一个给其参数加1的匿名函数 let add_one = |x| x + 1; // 也可以用代码块写: let add_one = |x| { x + 1 }; }
闭包可以接受多个参数:
#![allow(unused)] fn main() { let add = |x, y| x + y; let sum = add(1, 2); }
它们还能捕获环境中的变量:
#![allow(unused)] fn main() { let x = 42; let add_x = |y| x + y; let sum = add_x(1); }
必要时,你可以指定参数和/或返回类型的类型:
#![allow(unused)] fn main() { // 只指定输入类型 let add_one = |x: i32| x + 1; // 或者同时指定输入和输出类型,使用 `fn` 语法 let add_one: fn(i32) -> i32 = |x| x + 1; }
collect
使用组合子转换完迭代器后怎么办?
你可以使用 for
循环遍历转换后的值,或者将它们收集到集合中。
后者通过 collect
方法完成。
collect
消耗尽迭代器并将其元素收集到你选择的集合中。
例如,你可以将偶数的平方收集到一个 Vec
中:
#![allow(unused)] fn main() { let numbers = vec![1, 2, 3, 4, 5]; let squares_of_evens: Vec<u32> = numbers.iter() .filter(|&n| n % 2 == 0) .map(|&n| n * n) .collect(); }
collect
对其 返回类型 是泛型的。
因此,通常需要提供类型提示帮助编译器推断正确的类型。
在上面的例子中,我们标注了 squares_of_evens
的类型为 Vec<u32>
。
或者,你可以使用 turbofish语法 来指定类型:
#![allow(unused)] fn main() { let squares_of_evens = numbers.iter() .filter(|&n| n % 2 == 0) .map(|&n| n * n) // turbofish语法:`<method_name>::<type>()` // 因为 `::<>` 看起来像一条鱼,故得名turbofish .collect::<Vec<u32>>(); }
进一步学习
Iterator
的文档概述了标准库中迭代器可用的方法。- itertools 库定义了更多针对迭代器的 组合子。