Enumerator.new如何使用块传递?

时间:2014-05-21 12:16:08

标签: ruby-on-rails ruby

我对理解Enumerator.new方法的工作方式有所帮助。 假设文档中的示例:

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a
    a, b = b, a + b
  end
end

p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

循环中断条件在哪里,它如何知道循环应迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)?

2 个答案:

答案 0 :(得分:4)

修改我想我现在理解你的问题了,我仍然会在下面保留原来的答案。

y << ay.yield(a)的别名,基本上是带有返回值的sleep。每次使用next从枚举器请求值时,将继续执行,直到产生另一个值。


枚举器不需要枚举有限数量的元素,因此它们 无限。例如,fib.to_a永远不会终止,因为它试图构建一个包含无限元素的数组。

因此,调查员很好地表示无限系列,例如自然数,或者在你的情况下,斐波纳契数。枚举器的用户可以决定它需要多少个值,因此在您的示例take(10)中确定中断条件。

中断条件本身在Enumerator#take的实现中。出于演示目的,我们可以创建名为my_take的自己的实现:

class Enumerator
  def my_take(n)
    result = []
    n.times do
      result << self.next
    end
    result
  end
end

你当然可以在哪里&#34;精神上替代&#34;您的n.times循环使用经典C样式for (i=0; i<n; i++)。这是你的休息状况。 self.next是获取枚举数的下一个值的方法,您也可以在类之外使用它:

fib.next
#=> 1
fib.next
#=> 1
fib.next
#=> 2
fib.next
#=> 3

也就是说,您当然可以构建一个枚举有限数量值的枚举器,例如给定范围内的自然数,但这并非如此。然后,当您尝试调用StopIteration时,枚举器将引发next错误,但所有值都已枚举。在这种情况下,你有两个休息条件,可以这么说;那个先前打破的那个会胜利。 take实际上通过从错误中抢救来处理它,因此以下代码更接近实际实现(但是,take实际上是在C中实现的。

class Enumerator
  def my_take(n)
    result = []
    n.times do
      result << self.next
    end
    result
  rescue StopIteration
    # enumerator stopped early
    result
  end
end

答案 1 :(得分:4)

Enumerator在内部使用Fibers。您的示例等同于:

require 'fiber'

fiber = Fiber.new do
  a = b = 1
  loop do
    Fiber.yield a
    a, b = b, a + b
  end
end

10.times.map { fiber.resume }
#=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]