枚举器的循环魔法(同时没有中断和无循环)

时间:2017-12-06 17:19:19

标签: ruby enumerator

美好的一天。

简短问题How do loop inside a Enumerator.new knows where to stop?

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

示例#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&#34;魔法&#34;

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]

谢谢

1 个答案:

答案 0 :(得分:4)

请考虑以下事项:

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)说&#34;尝试收集此可枚举的最多n个值&#34;。

所以,回顾你原来的例子,我们有:

loop do
  x << a
  a *= 2
end

因为您在可枚举项上调用了take(4),所以Enumerator::Yielder会知道如果收集了4项,则会立即返回。

...另一方面,如果您尝试运行,例如enumer.to_a然后循环永远继续下去 - 因为没有任何条件可以提前退出!

根据我的发现,关于其工作原理的红宝石文档有点稀疏;但有this helpful description of the behaviour in the source code

/*
 * 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
 *
 */
相关问题