55、组合器
Combinators(组合器)
迭代器可以做的不仅仅是for循环。
如果我们查看Iteratortrait的文档,我们可以发现大量方法可以用来以各种方式转换、过滤和组合迭代器。
这里我们可以先了解一下最常见的一些组合器:
map可以将函数应用于迭代器的每个元素。filter仅保留满足特定条件(谓词)的元素。filter_map将filter和map合并为一个步骤。cloned将引用的迭代器转换为值的迭代器,对每个元素进行了克隆。enumerate返回一个新迭代器,该迭代器产生(index,value)对组(pair)。skip跳过迭代器的前n个元素。take在n个元素之后停止迭代器。chain将两个迭代器合二为一。
这些方法被称为组合器。
它们通常被串联使用,以简洁且易读的方式创建复杂的转换:
let numbers = vec![1, 2, 3, 4, 5];
// The sum of the squares of the even numbers
let outcome: u32 = numbers.iter()
.filter(|&n| n % 2 == 0)
.map(|&n| n * n)
.sum();
Closures(闭包)
上面的filter和map是以闭包作为参数使用的。
闭包就是匿名函数,即不使用我们习惯的fn语法定义的函数。
它们使用|args| body语法定义,其中args是闭包的参数,body是闭包的函数体。body可以是代码块或单个表达式。例如:
// An anonymous function that adds 1 to its argument
// 可以给函数参数加一的匿名函数
let add_one = |x| x + 1;
// Could be written with a block too:
// 也可以写成块状
let add_one = |x| { x + 1 };
闭包也可以包含多个参数:
let add = |x, y| x + y;
let sum = add(1, 2);
它们还可以从环境中捕捉变量:
let x = 42;
let add_x = |y| x + y;
let sum = add_x(1);
如果有必要,我们可以指定参数的类型,返回类型:
// Just the input type
// 对闭包里的参数指定类型
let add_one = |x: i32| x + 1;
// Or both input and output types, using the `fn` syntax
// 闭包既有输入类型,又有输出类型,绑定到fn类型
let add_one: fn(i32) -> i32 = |x| x + 1;
Collect(收集器)
当我们使用组合器来完成迭代器的转换的时候会发生什么?
我们可以使用for循环遍历转换后的值,或者将它们collect(收集)到一个容器中。
后者使用collcet方法来完成。
collect消耗迭代器,并将元素收集到我们选择的集合中。
例如,我们可以将偶数的平方收集到一个Vec中:
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语法来指定类型。
let squares_of_evens = numbers.iter()
.filter(|&n| n % 2 == 0)
.map(|&n| n * n)
// Turbofish syntax: `<method_name>::<type>()`
// Turbofish 语法: <方法名>::<类型>()
// It's called turbofish because `::<>` looks like a fish
// 之所以叫做turbofish,是因为::<>卡你来像一条鱼
.collect::<Vec<u32>>();
补充学习
Iterator的文档概述了std中可以用于迭代器的方法。itertoolscrate为迭代器定义了更多的组合器。
练习
这一节的练习就是要让我们实现一个使用迭代器和组合器的例子。还是用之前的Ticket例子。
题目如下:
// TODO: Implement the `to_dos` method. It must return a `Vec` of references to the tickets
// in `TicketStore` with status set to `Status::ToDo`.
use ticket_fields::{TicketDescription, TicketTitle};
#[derive(Clone)]
pub struct TicketStore {
tickets: Vec<Ticket>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Ticket {
pub title: TicketTitle,
pub description: TicketDescription,
pub status: Status,
}
#[derive(Clone, Debug, Copy, PartialEq)]
pub enum Status {
ToDo,
InProgress,
Done,
}
impl TicketStore {
pub fn new() -> Self {
Self {
tickets: Vec::new(),
}
}
pub fn add_ticket(&mut self, ticket: Ticket) {
self.tickets.push(ticket);
}
/* TODO */
}
题解如下:
pub fn to_dos(&self) -> Vec<&Ticket> {
self.tickets.iter().filter(|t| t.status == Status::ToDo).collect()
}
我们需要做的是,创建一个针对TicketStore里面的tickets的迭代器,然后这个迭代器使用filter方法,向里面传入一个闭包,参数列表是t,表示把迭代器的元素传进去,然后后面跟着Status::ToDo作为一个判断条件,筛选迭代器里面值为Status::Todo的元素,最后调用collect,再把这些数据收集起来,成为一个Vec<&Ticket>,就OK了。
这一节的学习就先到这了。
