Ruby Enumerators

一个enumerator是一个用来枚举其他对象的Enumerable对象。在Ruby1.8,需要require 'enumerator',在Ruby1.9已经内建,不需要再require,并且进行了增强。
Enumerators是类Enumerable::Enumerator,所以你可以直接new出来实例,但是通常使用to_enum或者使用enum_for(Object的方法)。如果没有参数,to_enum返回的enumerator,他的each直接delegate到目标对象的each。第一个参数是目标对象的迭代方法symbol,其他的参数都被传递给这个命名的方法。在Ruby 1.9,String已经不是Enumerable了,但是它定义了三个迭代方法each_char,each_byte,each_line。如果我们想要使用其他的Enumerable方法,比如
map,我们想基于each_char迭代器。我们可以创建一个enumerator:

s = "hello"
s.enum_for(:each_char).map{|c| c.succ } # => ["i", "f", "m", "m", "p"]

事实上,上面的例子在Ruby 1.9可以不用显式的使用to_enum或者enum_for,因为
times, upto, downto,step,each以及Enumerable相关的方法自动返回enumerator.
[quote]
irb(main):002:0> "hello".each_char.map{|c| c.succ }
=> ["i", "f", "m", "m", "p"]
[/quote]

enumerator = 3.times # An enumerator object
enumerator.each {|x| print x } # Prints "012"
10.downto(1).select {|x| x%2==0} # => [10,8,6,4,2]

我们举个简单的twice例子来说明这些方法的实现:

def twice
if block_given?
yield
yield
else
self.to_enum(:twice)
end
end

在1.9版本,enumerable对象定义了with_index方法:

s.each_char.with_index{|c,index| puts "#{index}: #{c}" }

迭代和并发修改:
和其他语言的迭代器一样,在迭代的过程修改迭代的对象,会产生不合预期的行为:

a = [1,2,3,4,5]
a.each {|x| puts "#{x},#{a.shift}" } # prints "1,1\n3,2\n5,3"

如果在多个线程共享一个容器,一个线程修改,另一个线程迭代,也会产生类似的
不符合预期的行为,要想避免,需要在迭代之前copy:

module Enumerable
def each_in_snapshot &block
snapshot = self.dup # Make a private copy of the Enumerable object
snapshot.each &block # And iterate on the copy
end
end

Enumerators有哪些用法?
1.作为proxy
就像前面说的Enumerable方法像map、select默认都会调用目标对象的each方法,
但是你想使用目标对象的each_byte,each_with_index方法来迭代,一个enumerator
就是一个简单的代理,它定义了each,并且将each方法delegate到enum_for
参数指定的目标对象的方法。

src = "hello"
puts src.enum_for(:each_byte).map { |b| "%02x" % b }.join(" ")

比如你想将一个数组传递到一个方法,因为数组是可变的,你不相信这个方法会不会改变
这个数组,因为你不期望这个方法会有副作用,那么你也可以使用enum_for得到enumerator,变成不变的代理对象:

# Call this method with an Enumerator instead of a mutable array.
# This is a useful defensive strategy to avoid bugs.
process(data.to_enum) # Instead of just process(data)

2.作为外部迭代器:
内部迭代器:迭代器自己控制迭代,将操作施用于每一个元素,比如each,select
外部迭代器:客户程序控制迭代,使用next来得到下一个元素。
外部迭代器比较灵活一些,比如可以很容易比较两个集合是否相等,但是内部迭代器
做不到。
Enumerator有each方法作为内部迭代器,来push数据到关联的代码,同时也提供了
next,作为客户端可以pull数据的外部迭代器。
使用next来获得下一个元素,但是如果没有元素了,next就会抛出StopIteration异常。
但Ruby1.9的Kernal.loop已经隐式的处理了这个异常,所以可以:

iterator = 9.downto(1)
loop do # Loop until StopIteration is raised
print iterator.next # Print next item
end
puts "bulabulabula..."

使用外部迭代来实现内部迭代:

module Iterable
include Enumerable

def each
loop {yeild self.next}
end
end

或者直接定义一个内部迭代方法,将有内部迭代的对象传递给他:

def iterate(iterator)
loop { yield iterator.next }
end
iterate(9.downto(1)) {|x| print x }



3.可以代表一段代码,进行Lazy evaluation.

fib = Enumerator.new { |y|
a = b = 1
loop {
y << a
a, b = b, a + b
}
}
fib.take(10) #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]


PS:
使用外部迭代器的一个重要的特点可以实现并行迭代:假如你有两个集合,需要
成对迭代。下面来自The Ruby programming language的并行迭代的例子:

Code View:

# Call the each method of each collection in turn.
# This is not a parallel iteration and does not require enumerators.
def sequence(*enumerables, &block)
enumerables.each do |enumerable|
enumerable.each(&block)
end
end

# Iterate the specified collections, interleaving their elements.
# This can't be done efficiently without external iterators.
# Note the use of the uncommon else clause in begin/rescue.
def interleave(*enumerables)
# Convert enumerable collections to an array of enumerators.
enumerators = enumerables.map {|e| e.to_enum }
# Loop until we don't have any more enumerators.
until enumerators.empty?
begin
e = enumerators.shift # Take the first enumerator
yield e.next # Get its next and pass to the block
rescue StopIteration # If no more elements, do nothing
else # If no exception occurred
enumerators << e # Put the enumerator back
end
end
end

# Iterate the specified collections, yielding tuples of values,
# one value from each of the collections. See also Enumerable.zip.
def bundle(*enumerables)
enumerators = enumerables.map {|e| e.to_enum }
loop { yield enumerators.map {|e| e.next} }
end

# Examples of how these iterator methods work
a,b,c = [1,2,3], 4..6, 'a'..'e'
sequence(a,b,c) {|x| print x} # prints "123456abcde"
interleave(a,b,c) {|x| print x} # prints "14a25b36cde"
bundle(a,b,c) {|x| print x} # '[1, 4, "a"][2, 5, "b"][3, 6, "c"]'


参考:《The Ruby programming language》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值