Wednesday, October 22, 2008

(3)迭代器和枚举对象

尽管我们前面说到的while,until,for循环是Ruby的核心部分,但是通常情况下我们是用的是另一个更特殊的方式,称为迭代器,迭代器是Ruby中非常值得关注的功能,很多介绍Ruby的文章都会出现类似这样的例子:
3.times { puts "thank you!" }  # 打印3次 感谢
data.each {|x| puts x } # 打印data中的每个元素
[1,2,3].map {|x| x*x } # 计算数组的平方
factorial = 1 # 计算n的阶乘
2.upto(n) {|x| factorial *= x }
times,each,map,upto都是迭代器,他们与紧随其后的进行交互。隐藏在这些复杂操作背后的是yield操作,yield子句会临时从迭代器方法获得一个控制,并把它交给迭代器调用的方法。特别是当控制逻辑贯穿整个代码块的迭代调用,当代模块结束迭代器方法会收回这个控制接着执行yield后的内容。下图是迭代器yield的执行过程:
如你所见,一个代码块是可以有参数的,||中包含的就是参数,就像你在方法定义中()中的参数一样--你可以定义一些列参数,所以yield子句就相当于方法调用。值得注意的是迭代器并不一定全部用于迭代,不如Ruby1.9中定义的tap方法。或者File.open,这个方法是一个迭代器,但是如果其中没有任何参数传入的话,它只代表你打开了一个文件,在open代码块结束的时候文件自动被关闭,这很好的避免了由于忘记关闭程序而带来的问题。
Numeric迭代
Ruby为整数类型定义了3中迭代器,upto方法执行从当前整数对象到传入的整数参数之间的范围内,每一个元素的循环调用。比如
4.upto(6) {|x| print x}   # => 打印"456"

一般来说一个n.upto(m)将执行m-n+1 次循环。downto方法类似于upto不过正好相反,它是从大的数字向小的数字循环。
Integer.times方法执行n次循环,比如:
3.times {|x| print x }    # => 打印"012"

如果你想使用浮点数进行迭代,你可以使用step方法,比如下面的例子中数字从0开始每次增加0.1直到数值达到Math::PI的值,并计算每个迭代只得sin值:
0.step(Math::PI, 0.1) {|x| puts Math.sin(x) }
枚举对象
Array,Range,Hash以及其他的一系列类,定义了一个each方法,这个方法会将集合内的每一个元素传递给代码块。这可能是Ruby中最常用的操作:
[1,2,3].each {|x| print x }   # => 打印"123"
(1..3).each {|x| print x } # => 打印"123" 等同于 1.upto(3)
不仅仅是这些类,Ruby中的IO库定义了each方法,这样你就可以循环的读取文件中的每一行并对其进行处理:
File.open(filename) do |f|       # 打开指定的文件,并将其内容赋值到变量f中
f.each {|line| print line } # 打印f的每一行
end # 结束块并关闭文件
很多类还基于each方法定义了更多专用的方法,比如each_with_index就是其中之一:
File.open(filename) do |f|
f.each_with_index do |line,number|
print "#{number}: #{line}"
end
end
除此之外,枚举对象还定义了一系列其他有用的方法,collect,select,reject和inject。collect方法循环枚举对象中的每个元素然后根据代码块中的计算结果创建一个新的枚举对象
squares = [1,2,3].collect {|x| x*x}   # => [1,4,9]

select 方法用于筛选块中条件判断为true的元素,并返回筛选的结果:
evens = (1..10).select {|x| x%2 == 0} # => [2,4,6,8,10]

reject 方法与select方法正好相反,他筛选块中条件判断为false或nil的元素,并返回筛选的结果:
odds = (1..10).reject {|x| x%2 == 0}  # => [1,3,5,7,9]


inject 方法有点复杂,解释一下,它有两个参数,第一个参数是这个迭代器的计算结果值,第二个参数代表迭代对象的循环时的每一个元素,每次迭代的返回值是第一个参数,在迭代结束的时候返回值就是最终两个参数的迭代计算结果。inject还有一个初始化参数房在括号中,如果这里有一个参数那么它将使枚举对象的第一个元素:
data = [2, 5, 3, 4]
sum = data.inject {|sum, x| sum + x } # => 14 (2+5+3+4)
floatprod = data.inject(1.0) {|p,x| p*x } # => 120.0 (1.0*2*5*3*4)
max = data.inject {|m,x| m>x ? m : x } # => 5 (largest element

No comments: