枚举器的循环魔术(没有中断,没有无限循环同时)



Enumerator.new 内部的循环如何知道在哪里停止?

现在更具描述性的示例。以下是它们都返回相同数组的代码片段:[1,2,4,8]。但是在第一个示例中breakloop条件存在,当第二个示例以某种方式停止时。

示例 #1 与break

def simple n
x  = []
a = 1
i = 0
loop do
x << a
a *= 2
i += 1
break unless i < n # in this case condition for stop used
end
x
end
p simple(4)== [1,2,4,8]

示例 #2 "神奇">

def enumer
Enumerator.new do |x|
a = 1
loop do # How do this loop know where to stop?
x << a
a *= 2
end
end
end
p enumer.take(4) == [1,2,4,8]

请考虑以下事项:

enum = Enumerator.new do |x|
x << "hello"
x << "world"
end
enum.take(1)
#=> ["hello"]
enum.take(100)
#=> ["hello", "world"]

这是怎么回事?

好吧,生成的变量xEnumerator::Yielder的一个实例。每当对变量调用<<yield时,都会将一个值附加到最终的结果数组中。

enum.take(n)说的是"尝试为此枚举收集最多n个值"。

因此,回顾您的原始示例,我们有:

loop do
x << a
a *= 2
end

由于你在枚举项上调用了take(4),因此如果收集了4项,Enumerator::Yielder将知道立即返回。

。另一方面,如果您尝试跑步,例如enumer.to_a,循环永远持续下去 - 因为它没有任何提前退出的条件!

从我发现的来看,关于它如何工作的 ruby 文档有点稀疏;但是源代码中对行为有这个有用的描述:

/*
* call-seq:
*   Enumerator.new(size = nil) { |yielder| ... }
*   Enumerator.new(obj, method = :each, *args)
*
* Creates a new Enumerator object, which can be used as an
* Enumerable.
*
* In the first form, iteration is defined by the given block, in
* which a "yielder" object, given as block parameter, can be used to
* yield a value by calling the +yield+ method (aliased as +<<+):
*
*   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]
*
* The optional parameter can be used to specify how to calculate the size
* in a lazy fashion (see Enumerator#size). It can either be a value or
* a callable object.
*
* In the second, deprecated, form, a generated Enumerator iterates over the
* given object using the given method with the given arguments passed.
*
* Use of this form is discouraged.  Use Kernel#enum_for or Kernel#to_enum
* instead.
*
*   e = Enumerator.new(ObjectSpace, :each_object)
*       #-> ObjectSpace.enum_for(:each_object)
*
*   e.select { |obj| obj.is_a?(Class) }  #=> array of all classes
*
*/