Enumerable 模块是一个很重要的模块,ruby中的很多类都有mix这个模块,比如array,hash等等。因此如果你想要自己定义一个collection,则不要继承一个存在的collection,而是应该mix Enumerable 模块。 数组是最常用,最有代表性的mix Enumerable模块的一个集合。因此下面的都会默认用数组来举例子 1 inject方法 先看一个很简单的例子: - nums = [3,5,7,9,11,13]
- sum = nums.inject(0) {|x,n| x+n }
这个结果就是 将nums的元素一次累加起来付给x,然后最终返回x.而inject的参数0的意思是x的初始值是0. 于是上面的代码其实也就等同于下面的代码: - sum = 0
- nums.each {|n| sum += n }
- puts sum
如果你忽略了inject的第一个参数,则它将会把所要迭代的数组的第一个元素作为那个累加值的默认值,然后从下一个元素开始迭代: - sum = nums.inject {|x,n| x+n }
-
-
-
- sum = nums[0]
- nums[1..-1].each {|n| sum += n }
下面可以看一下这个比较复杂的例子: - words = %w[ alpha beta gamma delta epsilon eta theta ]
- longest_word=words.inject do |best,w|
- w.length > best.length ? w : best
- end
- puts longest_word
这个例子的结果也就是words得到数组里面最长的那个元素 。 2 使用量词符. 使用any?和all?方法能够很容易测试一个集合: - nums = [1,3,5,8,9]
-
-
- flag1 = nums.any? {|x| x % 2 == 0 }
-
-
- flag2 = nums.all? {|x| x % 2 == 0 }
如果没有block参数时呢: - flag1 = list.all?
- flag1 = list.any?
-
3 partition 方法 简而言之,partition 方法就是用来分组的,也就是说,它可以将一个数组根据一定的规则分为不同的组. - nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
-
- odd_even = nums.partition {|x| x % 2 == 1 }
- # [[1,3,5,7,9],[2,3,4,6,8]]
-
- under5 = nums.partition {|x| x < 5 }
- # [[1,2,3,4],[5,6,7,8,9]]
-
- squares = nums.partition {|x| Math.sqrt(x).to_i**2 == x }
- # [[1,4,9],[2,3,5,6,7,8]]
代码很容易懂,也就是partition将数组中的每一个元素都传进它的block,然后用返回值来进行分组: 4 分组迭代 以前我们所介绍的迭代都是每次迭代一个元素,如果我们想要迭代一个组的时候,我们可以使用each_slice: - require 'enumerator'
-
- arr = [1,2,3,4,5,6,7,8,9,10]
- arr.each_slice(3) do |triple|
- puts triple.join(",")
- end
-
-
-
-
-
-
这里还有一个each_cons 方法,这个是从lisp得来的,看下面的代码: - require 'enumerator'
-
- arr = [1,2,3,4,5,6,7,8,9,10]
- arr.each_cons(3) do |triple|
- puts triple.join(",")
- end
-
-
-
-
-
-
-
-
-
-
5 使用Enumerator对象 Enumerator 一般是作为一个包装器,将一个iterator 方法转换成一个完全的Enumerable.当完成转换之后,这个对象将会有很多方法可以使用. 看下面的代码: - require 'enumerator'
-
- class Foo
- def every
- yield 3
- yield 2
- yield 1
- yield 4
- end
- end
-
- foo = Foo.new
-
-
- enum = Enumerable::Enumerator.new(foo,:every)
-
- enum.each {|x| p x }
- array = enum.to_a
- sorted = enum.sort
其实上面的那些转换代码和下面的代码是等价的: - enum = []
- foo.every {|x| enum << x }
我们下面还有另外一方法,来转换一个对象到Enumerable. 如果enumerator 被require,Object 将会有一个enum_for 方法,因此我们的转换方式将变为: - enum = foo.enum_for(:every)
对应于each_slice 和each_cons,当require了enumerator之后对象还会有enum_slice和enum_cons方法,这边要注意Enumerator.new方法,还能多加参数,它的后面的参数也就是所传进去的方法的参数: - array = [5,3,1,2]
-
- discrete = array.enum_slice(2)
-
-
- overlap = array.enum_cons(2)
-
-
- discrete.each {|x| puts x.join(",") }
-
-
-
-
- overlap.each {|x| puts x.join(",") }
-
-
-
-
6使用Generator 对象 一般ruby的迭代器都是内置的,迭代器通过它的block来操作逻辑. ruby还有一个外部的迭代器,这种迭代器它能够提供给你数据,让你来操作,比如IO中的getline函数。 generator 库能够转换内置的迭代器到外部的迭代器,它提供一个类似于IO的接口,这个接口含有next, rewind, 和 end?这样的方法: - require 'generator'
-
- array = [7,8,9,10,11,12]
-
- gen = Generator.new(array)
-
- what = gen.current
- where = gen.index
-
- while gen.current < 11 and gen.next?
- puts "#{gen.index},#{gen.next}"
- end
-
-
-
- puts gen.current
- puts gen.next
- puts gen.index
- puts gen.next?
- puts gen.next
- puts gen.next?
这里还有一个prev 方法,来得到上一个迭代的元素,而rewind 方法,则是重新将迭代的位置设置到开始的位置. 这边要注意的是generator 库是使用continuations实现的,而在ruby中这个是计算昂贵(computationally expensive,)的过程,因此如果如果有很多的大数字的话,使用generator 将会非常缓慢. |