Ruby 编程/参考/对象/Enumerable
Enumerator 在 Ruby 中以 Enumerable::Enumerator 的形式出现在 1.8.x 中,而在 1.9.x 中则以 (仅) Enumerator 的形式出现。
Enumerator 有多种不同的使用方法
- 作为“each”的代理
- 作为块中值的来源
- 作为外部迭代器
这是在 ruby 1.8 中引入的 Enumerator 的第一种使用方法。它解决了以下问题:Enumerable 方法如 #map 和 #select 会在你的对象上调用 #each,但如果你想使用其他方法(如 #each_byte 或 #each_with_index)进行迭代怎么办?
Enumerator 是一个简单的代理对象,它接收对 #each 的调用并将它重定向到基础对象上的不同方法。
require 'enumerator' # needed in ruby <= 1.8.6 only src = "hello" puts src.enum_for(:each_byte).map { |b| "%02x" % b }.join(" ")
对 'enum_for'(或等效地 'to_enum')的调用创建了 Enumerator 代理。它是以下内容的简写
newsrc = Enumerable::Enumerator.new(src, :each_byte) puts newsrc.map { |b| "%02x" % b }.join(" ")
在 ruby 1.9 中,Enumerable::Enumerator已更改为Enumerator
在 ruby 1.9 中,Enumerator.new 可以接收一个块,该块在调用 #each 时执行,并直接产生值。
block = Enumerator.new {|g| g.yield 1; g.yield 2; g.yield 3} block.each do |item| puts item end
“g << 1”是“g.yield 1”的替代语法
没有使用 Fiber 或 Continuation 等高级语言功能,这种形式的 Enumerator 很容易移植到 ruby 1.8
它与创建产生值的自定义对象非常相似
block = Object.new def block.each yield 1; yield 2; yield 3 end block.each do |item| puts item end
但是,它也为后面将要描述的可枚举对象的“延迟”评估奠定了基础。
ruby 1.9 还允许你反转 Enumerator,使其成为值的“拉取”源,有时也称为“外部迭代”。仔细观察它与上一个例子的区别
block = Enumerator.new {|g| g.yield 1; g.yield 2; g.yield 3} while item = block.next puts item end
控制流来回切换,第一次调用 #next 时会创建一个 Fiber,它保存调用之间的状态。因此,它的效率低于直接使用 #each 进行迭代。
当你调用 #next 且没有更多值时,会抛出一个StopIteration异常。这会被 while 循环静默捕获。StopIteration是IndexError的子类,而IndexError.
又是
require 'generator' block = Generator.new {|g| g.yield 1; g.yield 2; g.yield 3} while block.next? puts block.next end
StandardError
的子类ruby 1.8 中最接近的等效功能是 Generator,它是使用 Continuations 实现的。
延迟评估
Enumerator.new do |y| source.each do |input| # filter INPUT ... y.yield output # filter OUTPUT end end
class Enumerator def defer(&blk) self.class.new do |y| each do |*input| blk.call(y, *input) end end end end
在带有块的 Enumerator 中,要产生的目标会作为显式参数传递。这使得有可能设置方法调用的链,以便每个值从左到右传递到整个链,而不是在每一步都构建中间值数组。
res = (1..1_000_000_000).to_enum. defer { |out,inp| out.yield inp if inp % 2 == 0 }. # like select defer { |out,inp| out.yield inp+100 }. # like map take(10) p res
基本模式是带有块的 Enumerator,它处理输入值并为每个输入值产生(零个或多个)输出值。
所以,让我们把它包装在一个便利方法中
这个新的方法 'defer' 可以用作 select 和 map 的“延迟”形式。它不会构建值数组并在最后返回该数组,而是立即产生每个值。这意味着你可以更快地获得答案,并且它可以处理巨大甚至无限长的列表。示例
虽然我们从一个包含十亿个项目的列表开始,但在最后我们只使用了生成的第一个 10 个值,因此我们在此完成后停止迭代。你可以在 ruby 1.8 中使用相同的功能,使用facets 库。为了方便起见,它还提供了一个Denumberable 模块,其中包含常见 Enumerable 方法(如 map、select 和 reject)的延迟版本。
>> a = ["foo","bar","baz"] => ["foo", "bar", "baz"] >> b = a.each_with_index => #<Enumerable::Enumerator:0xb7d7cadc> >> b.each { |args| p args } ["foo", 0] ["bar", 1] ["baz", 2] => ["foo", "bar", "baz"] >>
返回 Enumerators 的方法[编辑 | 编辑源代码]从 1.8.7 开始,许多 Enumerable 方法如果未提供块,将返回 Enumerator。
src = "hello" puts src.each_byte.map { |b| "%02x" % b }.join(" ")
这意味着通常你不需要显式调用
=> ["foo", "bar", "baz"] >> b = a.select => #<Enumerable::Enumerator:0xb7d6cfb0> >> b.each { |arg| arg < "c" } => ["bar", "baz"] >>
enum_for
本页面上的第一个示例简化为- 这会导致非映射类方法出现一些奇怪的行为——当你稍后在对象上调用 #each 时,你必须为其提供“正确类型”的块。
- 更多 Enumerator 阅读
- [编辑 | 编辑源代码]
工单 707
enum_for 在 Strictly Untyped 博客中Generator 在 Anthony Lewis 的博客中
array = ['Superman','Batman','The Hulk'] array.each_with_index do |item,index| puts "#{index} -> #{item}" end # will print # 0 -> Superman # 1 -> Batman # 2 -> The Hulk
each_with_index 会使用项及其索引调用其块。
range = 1 .. 10 # find the even numbers array = range.find_all { |item| item % 2 == 0 } # returns [2,4,6,8,10]
array = ['Superman','Batman','Catwoman','Wonder Woman'] array = array.find_all { |item| item =~ /woman/ } # returns ['Catwoman','Wonder Woman']