组合子

迭代器能做的远不止是 for 循环!
如果你查阅 Iterator 特性的文档,你会发现大量的方法集合,可以用来以各种方式转换、过滤和组合迭代器。

这里列举一些最常见的:

  • map 对迭代器中的每个元素应用一个函数。
  • filter 只保留满足谓词的元素。
  • filter_map 结合了 filtermap 的功能,一步完成过滤和映射。
  • 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();
}

闭包

上述 filtermap 方法是怎么回事呢?
它们接受 闭包 作为参数。

闭包是 匿名函数,即不由我们熟悉的 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 库定义了更多针对迭代器的 组合子