55、组合器
Combinators
(组合器)
迭代器可以做的不仅仅是for
循环。
如果我们查看Iterator
trait的文档,我们可以发现大量方法可以用来以各种方式转换、过滤和组合迭代器。
这里我们可以先了解一下最常见的一些组合器:
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
中可以用于迭代器的方法。itertools
crate为迭代器定义了更多的组合器。
练习
这一节的练习就是要让我们实现一个使用迭代器和组合器的例子。还是用之前的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了。
这一节的学习就先到这了。