【问题标题】:Enumerator's loop magic (No break and no endless loop at the same time)枚举器的循环魔法(同时没有中断和无限循环)
【发布时间】:2023-03-22 07:31:01
【问题描述】:

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

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

break 的示例 #1

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]

【问题讨论】:

    标签: ruby enumerator


    【解决方案1】:

    考虑以下几点:

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

    这是怎么回事?

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

    enum.take(n) 表示“尝试为此枚举收集最多 n 值”。

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

    loop do
      x << a
      a *= 2
    end
    

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

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

    根据我的发现,关于其工作原理的 ruby​​ 文档有点少;但是有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
     *
     */
    

    【讨论】:

    • 感谢您的回答,内容丰富且清晰。
    猜你喜欢
    • 2012-09-22
    • 1970-01-01
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-11
    相关资源
    最近更新 更多