`
zcmit
  • 浏览: 17906 次
文章分类
社区版块
存档分类
最新评论

Rust中文翻译19

 
阅读更多

4.5 迭代器

我们来讨论一下迭代器.

还记得Rust的for循环么?有一个例子:
for x in 0..10 {
println!("{}", x);
}

现在你更了解Rust了,我们可以讨论它的工作细节了.区间(Ranges)(0..10)就是迭代器.一个迭代器可以重复的调用.next()方法,然后给我们返回一个序列.

像这样:
let mut range = 0..10;
loop {
match range.next() {
Some(x) => {
println!("{}", x);
},
None => { break }
}
}

我们使用了一个可变绑定到一个区间上, 这就是我们的迭代器.然后我们用一个内部的match在循环遍历这个迭代器.这个match作用在range.next()的返回值上,我们可以得到迭代器下一个元素的引用.next返回了一个Option<32>,在此例中,如果有值它是一个Some(i32)类型,否则就返回.如果我们得到了Some(i32),我们打印它,如果没有我们跳出循环.

这个代码基本上和for循环的例子是一样的.for循环的版本只不过是一个更趁手的loop/match/break的版本.

然而,for循环并不是使用迭代器的唯一例子.你可以写你自己的迭代器来实现iterator模板.这个内容超出了本书的范围,Rust提供了多种有用的迭代器来完成各种工作.讨论这些之前我们来看一下Rust的反模式(anti-pattern).那是使用区间的方式.

是的,我们刚刚看到了区间是多么的cool.但是区间还是很基础的.例如,如果你需要遍历一个向量的内容,你可以尝试这么写:

let nums = vec![1, 2, 3];

for i in 0..nums.len() {
println!("{}", nums[i]);
}

Page 96

这样写比使用迭代器要糟糕.你可以这样直接遍历迭代器:
let nums = vec![1, 2, 3];

for num in &nums {
pirntln!("{}", num);
}

这么做有连个原因.第一,这更直接的表达了我们的意思.我们迭代整个向量, 而不是迭代向量的坐标,然后通过向量坐标来访问.第二,这个版本更有效率:第一个版本使用了额外的绑定检查,因为它使用了下标,第二个版本没有绑定检查.这对于迭代器来说很普遍:我们可以忽略没有必要的绑定检查,但是我们知道它是安全的.

还有一点我们并不是100%清楚的,就是println!的工作方式.num是一个&i32类型.也就是一个i32的类型的引用,而不是i32类型自己.println!帮我们处理了类型推导,但是我们并不知道这一点.这个代码也可以工作:

let nums = vec![1, 2, 3];

for num in &nums {
println!("{}", *num);
}

现在我们显示解引用了num.为什么&nums给我们引用呢?第一,我们显示声明了要使用引用.第二,如果它给我们返回了值本身,我们就变成了它的所有者,这就回导致内存拷贝.使用引用的话,我们只是借用了一个数据值的引用,不需要拷贝.

所以,现在我们建立了一个不经常需要的区间,我们来讨论一下你真正需要的吧.

我们有3种相关的类型:iterators, iterator adapters,和consumers.区别是:
  • iterators得到一个值的序列
  • iterator adapter操作一个迭代器,生成一个新的迭代器,输出顺序不一样
  • consumers操作一个迭代器,产生最后的值的集合.
我们先来看consumers,因为你已经见过迭代器了.

Page 97

Consumers:

一个consumer操作一个迭代器,返回一种类型的值.最常用的consumer就是collect().这个代码不能编译,仅仅说明使用方法:
let ont_to_one_hundred = (1.101).collect();

正如你看到的,我们调用了collect().collect()可以接收所有迭代器传给他的值,然后返回这些值的集合.那么为什么这个不能编译呢?Rust没法知道你的collect想要什么类型的值.你必须让他知道.这个是可以编译的版本:
let one_to_one_hundred = (1..101).collect::<Vec<i32>>();

如果你还记得::<>语法可以给我们一个类型提示,那么我们就告诉了编译器我们需要一个整数类型的向量.你不用总是使用整个类型声明.可以使用一个_来代替:
let one_to_one_hundred = (1..101).collect::<Vec<_>>();

这句话可以读作"Collect到Vec<T>中,推断T类型"._通常被称为类型占位符.
collect()是最常用的consumer,但是还有其他的.find()是一个:

let greater_than_forty_two = (0..100).find(|x| *x > 42);
match greater_than_forty_two {
Some(_) => println!("We got some numbers!");
None => println!("No numbers found :(");
}

find使用了一个闭包,遍历了迭代器元素的引用.这个闭包返回真,如果元素就是我们要找的话,否则返回false.我们有可能找不到匹配的元素,find返回了一个Option而不是元素值本身.

另一个重要的consumer是fold.像这样子:

let sum = (1..4).fold(0, |sum, x| sum + x);

fold()是一个cunsumer像这样工作:fold(base, |accumulator, element| ...).他有两个参数:第一个是base.第2个是一个闭包,也有两个参数.第一个是accumulator,第二个是element.对于每一个迭代,闭包被调用,返回值是accumulator在下一个迭代的值.在第一个迭代时,base就是accumulator.

好了,有一点复杂了.让我们看看所有这些值:
base accumulator elemnt closure result
0 0 1 1
0 1 2 3
0 3 3 6

所以,0就是base,sum就是accumulator,x就是element.在第一次迭代中,我们设置了sum为0,x是第一个元素1.我们将sum和x相加,也就是0+1=1.在第二次迭代中,sum称为了accumulator,element是第二个元素2,1+2=3.它是最后一次迭代的accumulator.那时,x是最后一个元素3,3+3=6,这就是最后的sum.1+2+3=6,那就是我们得到的返回值.

WOW.fold在最初你使用它的时候看起来很奇怪,但是一旦你开始用他,它会经常出现.任何时候你有了一个列表,你要一个单一的返回值,fold是最合适的.

cunsumers很重要,因为我们还没有介绍迭代的一个附加属性:laziness.让我们在多看几个例子,你就知道为什么cunsumers很重要了.

Iterators:

正如我们前面说过的,一个迭代器就是可以在它上面反复调用next()的,返回一个序列的东西.因为我们要调用这个方法,也就意味着迭代器可以懒惰(lazy)然后不产生那些值.这个代码并不真正产生1-100个数,只是生成一个代表这个序列的一个值:

let nums = 1..100;

因为我们没有做任何事在这个区间上,它不会产生序列.让我们添加cunsumer:

let nums = (1..100).collect::<Vec<i32>>();

现在,collect会要求这个区间给他一些数,所以这时才会真正生成这些数.
区间是迭代器的两个基础之一.另一个是iter().iter()可以把一个向量转变成一个简单的iterator返回给你每一个元素的值:

let nums = vec![1, 2, 3];
for num in nums.iter() {
println!("{}", num);
}

这两个基本的迭代器会让你用的很好的.还有一些高级的迭代器,包括无限迭代器.
对于迭代器我们说的够多了.Iterator adapters是最后一个我们需要说的关于迭代器的概念了.去看看吧!

Iterator adapters:

iterator adapters得到一个迭代器并且修改它,产生一个新的迭代器.最简单的是map:

(1..100).map(|x| x + 1);

map被称为在另一个迭代器上工作,产生一个新的迭代器,其中的元素引用有一个闭包,这个闭包有一个参数.所以这个代码给了我们2-100的数.很好,很接近了!如果你编译这个代码,你会得到一个警告:



Laziness再一次让体现了它的存在!这个闭包不会被执行.此例不会打印任何数字:
(1..100).map(|x| println!("{}", x));

如果你正想使用闭包迭代器的附加影响,可以试用for循环替代.

有很多有用的iterator adapter.take(n)可以返回原始迭代器的后n个元素的新的迭代器.这个迭代器没有作用在原始迭代器的附加效果.让我们试一下无限循环:

for i in (1..).step_by(5).take(5) {
println!("{}", i);
}

它会打印
1
6
11
16
21

filter()是一个可以带一个参数的闭包的adapter.这个闭包返回true或者false.新的filter()迭代器只产生那些闭包返回式true的元素:

for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}

这个打印了1到100的偶数.(因为filter迭代器并没有消费任何元素,所以我们传给了它引用.filter迭代器自己推断&x是一个整数类型.)

你可以把三个迭代器一起使用:由一个迭代器开始,适配到一个新的迭代器然后消费它的结果.来看:

(1..1000)
.filter(|&x| x % 2 == 0)
.filter(|&x| x % 3 == 0)
.take(5)
.collect::<Vec<i32>>();

这个迭代器返回一个向量,包含6, 12, 18, 24, 30.

上述仅仅是关于iterators, iterators adapters和cunsumers的一个浅偿.还有很多可以帮助你的迭代器.迭代器提供了一个安全的,高效的操作所有列表类型的方法.他们开始可能看起来不太寻常,但是你可以试用他们,他们会正常工作的.要查看所有这些类型,你可以看迭代器的帮助文档(http://doc.rust-lang.org/std/iter/index.html.)
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics