feiyang10086 发表于 2011-01-21 14:47

ruby way之Enumerables 及Iterator的区别

ruby way之Enumerables 及Iterator的区别



Enumerable 模块是一个很重要的模块,ruby中的很多类都有mix这个模块,比如array,hash等等。因此如果你想要自己定义一个collection,则不要继承一个存在的collection,而是应该mix Enumerable 模块。

数组是最常用,最有代表性的mix Enumerable模块的一个集合。因此下面的都会默认用数组来举例子

1 inject方法

先看一个很简单的例子:

Ruby代码nums =
sum = nums.inject(0) {|x,n| x+n }这个结果就是 将nums的元素一次累加起来付给x,然后最终返回x.而inject的参数0的意思是x的初始值是0.

于是上面的代码其实也就等同于下面的代码:

Ruby代码sum = 0
nums.each {|n| sum += n }
puts sum如果你忽略了inject的第一个参数,则它将会把所要迭代的数组的第一个元素作为那个累加值的默认值,然后从下一个元素开始迭代:

Ruby代码sum = nums.inject {|x,n| x+n }

#和下面的代码是等价的

sum = nums
nums.each {|n| sum += n }下面可以看一下这个比较复杂的例子:

Ruby代码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?方法能够很容易测试一个集合:

Ruby代码nums =

# 这些元素有一个奇数吗?
flag1 = nums.any? {|x| x % 2 == 0 }    # true

#这些元素都是偶数吗?
flag2 = nums.all? {|x| x % 2 == 0 }    # false如果没有block参数时呢:

Ruby代码flag1 = list.all?   # list包含no falses 或者nils
flag1 = list.any?   # list contains at least one true value (non-nil
                  #   or non-false)3 partition 方法

简而言之,partition 方法就是用来分组的,也就是说,它可以将一个数组根据一定的规则分为不同的组.

Java代码nums =

odd_even = nums.partition {|x| x % 2 == 1 }
# [,]

under5 = nums.partition {|x| x < 5 }
# [,]

squares = nums.partition {|x| Math.sqrt(x).to_i**2 == x }
# [,]代码很容易懂,也就是partition将数组中的每一个元素都传进它的block,然后用返回值来进行分组:

4 分组迭代

以前我们所介绍的迭代都是每次迭代一个元素,如果我们想要迭代一个组的时候,我们可以使用each_slice:

Ruby代码require 'enumerator'

arr =
arr.each_slice(3) do |triple|
puts triple.join(",")
end
#这里传入的3,也就意味着,每次传进去三个元素.
# Output:
# 1,2,3
# 4,5,6
# 7,8,9
# 10这里还有一个each_cons 方法,这个是从lisp得来的,看下面的代码:

Ruby代码require 'enumerator'

arr =
arr.each_cons(3) do |triple|
puts triple.join(",")
end

# Output:
# 1,2,3
# 2,3,4
# 3,4,5
# 4,5,6
# 5,6,7
# 6,7,8
# 7,8,9
# 8,9,105 使用Enumerator对象

Enumerator 一般是作为一个包装器,将一个iterator 方法转换成一个完全的Enumerable.当完成转换之后,这个对象将会有很多方法可以使用.

看下面的代码:

Ruby代码require 'enumerator'

class Foo
def every
    yield 3
    yield 2
    yield 1
    yield 4
end
end

foo = Foo.new

# 传递一个对象和它的iterator 名字
enum = Enumerable::Enumerator.new(foo,:every)

enum.each {|x| p x }   # Print out the items
array = enum.to_a      #
sorted = enum.sort       # 其实上面的那些转换代码和下面的代码是等价的:

Ruby代码enum = []
foo.every {|x| enum << x }我们下面还有另外一方法,来转换一个对象到Enumerable.

如果enumerator 被require,Object 将会有一个enum_for 方法,因此我们的转换方式将变为:

Ruby代码
enum = foo.enum_for(:every)


对应于each_slice 和each_cons,当require了enumerator之后对象还会有enum_slice和enum_cons方法,这边要注意Enumerator.new方法,还能多加参数,它的后面的参数也就是所传进去的方法的参数:

Ruby代码array =

discrete = array.enum_slice(2)
# Same as: Enumerable::Enumerator.new(array,:each_slice,2)

overlap= array.enum_cons(2)
# Same as: Enumerable::Enumerator.new(array,:each_cons,2)

discrete.each {|x| puts x.join(",") }
# Output:
# 5,3
# 1,2

overlap.each {|x| puts x.join(",") }
# Output:
# 5,3
# 3,1
# 1,26使用Generator 对象


一般ruby的迭代器都是内置的,迭代器通过它的block来操作逻辑.

ruby还有一个外部的迭代器,这种迭代器它能够提供给你数据,让你来操作,比如IO中的getline函数。

generator 库能够转换内置的迭代器到外部的迭代器,它提供一个类似于IO的接口,这个接口含有next, rewind, 和 end?这样的方法:

Ruby代码require 'generator'

array =

gen = Generator.new(array)

what= gen.current    # 7
where = gen.index      # 0(same as pos)

while gen.current < 11 and gen.next?   
puts "#{gen.index},#{gen.next}"
end

#0,7 1,8 2,9 3,10

puts gen.current       # 11
puts gen.next          # 11
puts gen.index         # 5    (index same as pos)
puts gen.next?         # true   (next? same as end?)
puts gen.next          # 12
puts gen.next?         # false

zuerrong 发表于 2011-01-21 15:40

不错啊。。是转贴还是原创?

2gua 发表于 2011-01-24 14:31

不错不错啊。

gr33n 发表于 2012-02-07 16:08

不错啊。这些不错。

rubyish 发表于 2012-02-08 18:27

很有代表性啊。
页: [1]
查看完整版本: ruby way之Enumerables 及Iterator的区别