我对理解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]
循环中断条件在哪里,它如何知道循环应迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)?
答案 0 :(得分:4)
修改我想我现在理解你的问题了,我仍然会在下面保留原来的答案。
y << a
是y.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]