rust学习之迭代器

迭代器基本特征

迭代器允许我们迭代一个连续的集合,例如数组、动态数组 VecHashMap 等,我们只需关注集合中的元素如何处理,而不关心如何去访问等问题。从用途来看,迭代器跟 for 循环颇为相似,都是去遍历一个集合。在rust中,数组不是迭代器,但数组实现了 IntoIterator 特征,Rust 通过 for 语法糖,自动把实现了该特征的数组类型转换为迭代器,最终可以直接对一个数组进行迭代。IntoIterator 特征拥有一个 into_iter 方法,因此可以显式的把数组转换成迭代器:

let arr = [1, 2, 3];
for v in arr.into_iter() {  //效果同for v in arr {
    println!("{}", v);
}

let v1_iter = v1.iter(); //创建了一个迭代器 v1_iter; 要实现Iterator 特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item

fn main() {
    let arr = [1, 2, 3];
    let mut arr_iter = arr.into_iter();

    assert_eq!(arr_iter.next(), Some(1));
    assert_eq!(arr_iter.next(), Some(2));
    assert_eq!(arr_iter.next(), Some(3));
    assert_eq!(arr_iter.next(), None);
}

将 arr 转换成迭代器后,通过调用其上的 next 方法,获取了 arr 中的元素,有两点需要注意:

  • next 方法返回的是 Option 类型,当有值时返回 Some(i32),无值时返回 None
  • 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 Some(1)Some(2)Some(3)
  • 手动迭代必须将迭代器声明为 mut 可变,因为调用 next 会改变迭代器其中的状态数据(当前遍历的位置等),而 for 循环去迭代则无需标注 mut,因为它会帮我们自动完成

next 方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None

IntoIterator与Iterator的区别:IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iteriter 等方法变成一个迭代器。而Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next

从上面可以看出into_iter与iter都可以将数组转换为迭代器,他们的区别如下:

  • into_iter 会夺走所有权,调用 next 方法返回的类型是 Some(T)
  • iter 是借用,调用 next 方法返回的类型是 Some(&T)
  • iter_mut 是可变借用,调用 next 方法返回的类型是 Some(&mut T)

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

    for v in values.into_iter() {
        println!("{}", v)
    }

    // 下面的代码将报错,因为 values 的所有权在上面 `for` 循环中已经被转移走
    // println!("{:?}",values);

    let values = vec![1, 2, 3];
    let _values_iter = values.iter();

    // 不会报错,因为 values_iter 只是借用了 values 中的元素
    println!("{:?}", values);

    let mut values = vec![1, 2, 3];
    // 对 values 中的元素进行可变借用
    let mut values_iter_mut = values.iter_mut();

    // 取出第一个元素,并修改为0
    if let Some(v) = values_iter_mut.next() {
        *v = 0;
    }

    // 输出[0, 2, 3]
    println!("{:?}", values);
}

消费者与适配器

消费者是迭代器上的方法,它会消费掉迭代器中的元素,都依赖 next 方法来消费元素,只要迭代器上的某个方法 A 在其内部调用了 next 方法,那么 A 就被称为消费性适配器(消费掉迭代器,然后返回一个值),同理,迭代器适配器就是返回一个新的迭代器,这是实现链式方法调用的关键:v.iter().map().filter()...。与消费者适配器不同,迭代器适配器是惰性的,意味着需要一个消费者适配器来收尾,最终将迭代器转换成一个具体的值

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

    let v1_iter = v1.iter();

    let total: i32 = v1_iter.sum(); //sum就是消费者适配器

    assert_eq!(total, 6);

    // v1_iter 是借用了 v1,因此 v1 可以照常使用
    println!("{:?}",v1);

    // 以下代码会报错,因为 `sum` 拿到了迭代器 `v1_iter` 的所有权
    // println!("{:?}",v1_iter);
}
let v1: Vec<i32> = vec![1, 2, 3];
//v1.iter().map(|x| x + 1); 这样写会参数告警,因为迭代器适配器map是惰性的,这里不产生任何效果
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

assert_eq!(v2, vec![2, 3, 4]);

collect方法是一个消费者适配器,它可以将一个迭代器中的元素收集到指定类型中,比如这里为 v2 标注了 Vec<_> 类型,就是为了告诉 collect:请把迭代器中的元素消费掉,然后把值收集成 Vec<_> 类型,至于为何使用 _,因为编译器会帮我们自动推导。

map方法会对迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值,该操作是通过闭包 |x| x + 1 来完成:最终迭代器中的每个值都增加了 1,从 [1, 2, 3] 变为 [2, 3, 4]

以下是使用collect收集成 HashMap 集合的一个示例:

use std::collections::HashMap;
fn main() {
    let names = ["sunface", "sunfei"];
    let ages = [18, 18];
    let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();

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

zip 是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)> 这样的新的迭代器,在此处就是形如 [(name1, age1), (name2, age2)] 的迭代器。然后再通过 collect 将新迭代器中(K, V) 形式的值收集成 HashMap<K, V>

闭包作为适配器参数

之前的 map 方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值:

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

filter 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 s 是来自迭代器中的值,然后使用 s 跟外部环境中的 shoe_size 进行比较,若相等,则在迭代器中保留 s 值,若不相等,则从迭代器中剔除 s 值,最终通过 collect 收集为 Vec<Shoe> 类型(类型推导,shoes_in_size函数的返回类型)

实现Iterator特征

只要为自定义类型实现 Iterator 特征就可以创建自己的迭代器,例如为计数器实现 Iterator 特征:首先为计数器 Counter 实现一个关联函数 new,用于创建新的计数器实例。

struct Counter {
    count: u32,
}

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

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
        }
    }
}

 let mut counter = Counter::new(); //创建Counter实例
//使用新建的Counter进行迭代
assert_eq!(counter.next(), Some(1)); 
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);

Iterator 特征中,不仅仅是只有 next 一个方法,还有其它方法,但都具有默认实现,所以无需像 next 这样手动去实现。

let sum: u32 = Counter::new()
    .zip(Counter::new().skip(1))
    .map(|(a, b)| a * b)
    .filter(|x| x % 3 == 0)
    .sum();
assert_eq!(18, sum);

其中 zipmapfilter 是迭代器适配器:

  • zip 把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将形如 [1, 2, 3, 4, 5] 和 [2, 3, 4, 5] 的迭代器合并后,新的迭代器形如 [(1, 2),(2, 3),(3, 4),(4, 5)]
  • map 是将迭代器中的值经过映射后,转换成新的值[2, 6, 12, 20]
  • filter 对迭代器中的元素进行过滤,若闭包返回 true 则保留元素[6, 12],反之剔除
  • sum 是消费者适配器,对迭代器中的所有元素求和,最终返回一个 u32 值 18

Iterator 特征上的方法 enumerate是一个迭代器适配器,它产生一个新的迭代器,其中每个元素均是元组 (索引,值) 

let v = vec![1u64, 2, 3, 4, 5, 6];
let val = v.iter()
    .enumerate()
    // 每两个元素剔除一个
    // [1, 3, 5]
    .filter(|&(idx, _)| idx % 2 == 0)
    .map(|(_, val)| val)
    // 累加 1+3+5 = 9
    .fold(0u64, |sum, acm| sum + acm);

println!("{}", val);

迭代器是 Rust 的 零成本抽象,在获得更高的表达力的同时,也不会导致运行时的损失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>