进阶篇-迭代器

  • 木头
  • 更新于 2023-03-13 17:32
  • 阅读 1953

迭代器的使用,自定义迭代器

迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。

Rust 中,迭代器是 惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果。例如我们调用定义于 Vec 上的iter方法在一个 vector`` v1 上创建了一个迭代器:

fn main() {
    let v = vec![1, 2, 3, 4];

    let v_iter = v.iter();
}

迭代器被储存在v1_iter 变量中。一旦创建迭代器之后,可以选择用多种方式利用它。我们使用 for循环来遍历一个数组并在每一个项上执行了一些代码。

将迭代器的创建和for 循环中的使用分开。迭代器被储存在 v_iter 变量中,而这时没有进行迭代。一旦 for 循环开始使用v_iter,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:

fn main() {
    let v = vec![1, 2, 3, 4];

    let v_iter = v.iter();

    for v in v_iter {
        println!("{}", v);
    }
}

在标准库中没有提供迭代器的语言中,我们可能会使用一个从0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。

迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector这样可索引的数据结构。

Iterator trait 和 next 方法

迭代器都实现了一个叫做 Iterator 的定义于标准库的 trait。这个 trait的定义看起来像这样:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;

    // ....默认实现
}

注意这里有一个我们还未讲到的新语法:type ItemSelf::Item,他们定义了 trait 的 关联类型(associated type)。不过现在只需知道这段代码表明实现 Iterator trait 要求同时定义一个 Item 类型,这个 Item类型被用作 next方法的返回值类型。换句话说,Item类型将是迭代器返回元素的类型。

nextIterator实现者被要求定义的唯一方法。next 一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None

可以直接调用迭代器的 next 方法所得到的值:

fn main() {
    let v = vec![1, 2, 3, 4];

    let mut v_iter = v.iter();

    if let Some(v) = v_iter.next() {
        println!("{}", v);
    }

    if let Some(v) = v_iter.next() {
        println!("{}", v);
    }

    if let Some(v) = v_iter.next() {
        println!("{}", v);
    }

    if let Some(v) = v_iter.next() {
        println!("{}", v);
    }

    if let Some(v) = v_iter.next() {
        println!("{}", v);
    } else {
        println!("结束");
    }
}

注意v_iter 需要是可变的:在迭代器上调用next方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 消费(consume)了,或使用了迭代器。每一个 next调用都会从迭代器中消费一个项。使用 for循环时无需使 v_iter 可变因为 for 循环会获取 v_iter 的所有权并在后台使 v_iter可变。

另外需要注意到从 next 调用中得到的值是 vector 的不可变引用。iter 方法生成一个不可变引用的迭代器。如果我们需要一个获取 v 所有权并返回拥有所有权的迭代器,则可以调用into_iter 而不是 iter。类似的,如果我们希望迭代可变引用,则可以调用iter_mut 而不是 iter

fn main() {
    let mut v = vec![1, 2, 3, 4];

    let mut v_iter = v.iter_mut();

    if let Some(v) = v_iter.next() {
        *v = 5;
    }
}

消费适配器

Iterator trait有一系列不同的由标准库提供默认实现的方法;你可以在 Iterator trait 的标准库API 文档中找到所有这些方法。一些方法在其定义中调用了 next 方法,这也就是为什么在实现Iterator trait 时要求实现 next 方法的原因。

这些调用next方法的方法被称为 消费适配器(consuming adaptors),因为调用他们会消耗迭代器。一个消费适配器的例子是 sum 方法。这个方法获取迭代器的所有权并反复调用 next 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和:

fn main() {
    let v = vec![1, 2, 3, 4];

    let v_iter = v.iter();

    let s: i32 = v_iter.sum();

    println!("{}", s);
}

调用 sum 之后不再允许使用v1_iter 因为调用 sum时它会获取迭代器的所有权。如果还需要使用则需要重新创建迭代器。

迭代器适配器

Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。

下面是调用迭代器适配器方法 map 的例子,该 map 方法使用闭包来调用每个元素以生成新的迭代器。这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。:

fn main() {
    let v = vec![1, 2, 3, 4];

    v.iter().map(|x| x + 1);
}

不过这些代码会产生一个警告,代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。

也就是说,除非你调用一个消费者,不然,你的操作,永远也不会被调用到!

为了修复这个警告并消费迭代器获取有用的结果,我们将使用 collect 方法。这个方法消费迭代器并将结果收集到一个数据结构中。

我们将遍历由 map 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加 1 的结果:

fn main() {
    let v = vec![1, 2, 3, 4];

    let v1: Vec<_> = v.iter().map(|x| x + 1).collect();

    println!("{:?}", v1);
}

因为map 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。

现在,我们知道了map,那当然还有filterfilter接受一个闭包函数,返回一个布尔值,返回true的时候表示保留,false丢弃:

fn main() {
    let v = vec![1, 2, 3, 4];

    let v1: Vec<_> = v.into_iter().filter(|x| *x < 3).collect();

    println!("{:?}", v1);
}

这里注意需要获取所有权的迭代器。

自定义迭代器

我们可以在 vector上调用 iterinto_iteriter_mut 来创建一个迭代器,也可以使用标准库中其他的集合类型创建迭代器,如 hashmap。除此之外,我们可以通过实现Iterator trait 来创建我们自定义的迭代器,并且就像前面我们说的,唯一的要求就是实现next方法,之后,我们就可以使用其他由 Iterator trait默认实现的所有方法!

我们将会创建一个从15进行计数的迭代器来作为例子。首先让我们创建一个自定义的结构体用于存放计数:

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Self {
        Counter { count: 0 }
    }
}

接下来我们为 Counter 实现 Iterator trait,并实现 next方法自定义迭代器的行为:

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

我们将迭代器的关联类型 Item设置为 u32,这代表迭代器的返回值为 u32 类型,并且当count 大于或等于 5 时,next会返回 None,迭代结束。

使用我们自定义的迭代器 next 方法:

fn main() {
    let mut c = Counter::new();

    for _ in 0..10 {
        if let Some(v) = c.next() {
            println!("{}", v);
        } else {
            println!("结束");
            break;
        }
    }
}

运行库结果:

1
2
3
4
5
结束
  • 原创
  • 学分: 4
  • 分类: Rust
  • 标签:
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
125 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。