55、组合器

Combinators(组合器)

 

迭代器可以做的不仅仅是for循环。

如果我们查看Iteratortrait的文档,我们可以发现大量方法可以用来以各种方式转换、过滤和组合迭代器。

 

这里我们可以先了解一下最常见的一些组合器:

  • map可以将函数应用于迭代器的每个元素。
  • filter仅保留满足特定条件(谓词)的元素。
  • filter_mapfiltermap合并为一个步骤。
  • cloned将引用的迭代器转换为值的迭代器,对每个元素进行了克隆。
  • enumerate返回一个新迭代器,该迭代器产生(index,value)对组(pair)。
  • skip跳过迭代器的前n个元素。
  • taken个元素之后停止迭代器。
  • 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(闭包)

 

上面的filtermap是以闭包作为参数使用的。

 

闭包就是匿名函数,即不使用我们习惯的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>>();

 

补充学习

 

练习

 

这一节的练习就是要让我们实现一个使用迭代器和组合器的例子。还是用之前的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了。

 

这一节的学习就先到这了。

阅读剩余
THE END