【问题标题】:Ruby Enumerator and Enumerable interaction: StopIterator dependencyRuby Enumerator 和 Enumerable 交互:StopIterator 依赖
【发布时间】:2018-03-09 23:47:08
【问题描述】:

我刚刚遇到了一些有趣的 Enumerator 行为。 Enumerator 似乎对 Enumerable 中的位置存在某种依赖性 - 一旦您查看了 Enumerable 的结尾并且引发了 StopIteration,Enumerator 就不会注意到 Enumerable 的扩展。

两个例子演示:

a=[1, 2, 3]
e=a.each
 => #<Enumerator: [1, 2, 3]:each> 
2.4.0 :027 > e.next
 => 1 
2.4.0 :028 > a.insert(1, 4)
 => [1, 4, 2, 3] 
2.4.0 :029 > e.next
 => 4 
2.4.0 :031 > e.next
 => 2 

好的,到目前为止,一切都很好。但是这个呢。让我们定义一个方法来在我们到达末尾时扩展数组:

def a_ext(a,enum)
  enum.peek
rescue StopIteration
  a << a[-1] + 1
end

现在让我们看看当我们使用它时会发生什么

2.4.0 :012 > a=[1, 2, 3]
 => [1, 2, 3] 
2.4.0 :013 > e = a.each
 => #<Enumerator: [1, 2, 3]:each>
2.4.0 :016 > 3.times{e.next} 
 => 3 

我们已经到达数组的末尾 - 所以调用a_ext 来扩展数组

2.4.0 :018 > a_ext(a,e)
 => [1, 2, 3, 4] 
2.4.0 :019 > e.peek
StopIteration: iteration reached an end

????!!

看起来一旦你点击了 StopIteration,Enumerator 就不会再次检查 Array(我猜一般来说是一个 Enumerable)是否已扩展。

这是预期的行为吗?一个错误?一个功能?

您为什么要这样做。好吧 - 使用哈希,您可以通过传递 Hash::new 一个块来设置默认值 - 您可以将一个块传递给 Array::new。但是Array::new 作为参数的块只有索引作为键,而不是数组和索引(如 Hash::new,其块产生哈希和键)。因此,这使得构建一个可以在枚举时扩展的数组变得非常丑陋和困难。

例如,想象一个约会日记,您想在其中枚举以查找第一个空闲日。这自然是一个 Array 而不是 Hash(因为它是有序的),但是在迭代它时很难扩展。

想法?

【问题讨论】:

  • 恕我直言,您应该避免在迭代数组时更新数组以避免不一致。在您的示例中,您可以使用 findfind_index 而不是使用枚举器。

标签: ruby enumerator


【解决方案1】:

我相信原因是StopIteration 有一个result 属性,如果且只有迭代循环已经结束,这基本上是已知的。考虑以下三个示例:

[1,2,3].enum_for(:reduce, :*)          # #1, delegated to Array#reduce

[1,2,3].enum_for(:each, method(:puts)) # #2, delegated to Array#each

o = Object.new
def o.each { yield 1; yield 2; yield 3; 100 } # #3

一旦抛出(创建)异常,就应该知道该值(顺便说一句,在第一种情况下是 6,在第二种情况下是 [1,2,3],在第三种情况下是 100。)这基本上意味着允许重新-进入循环会引入不一致(该值存在,但不再正确。)

枚举器必须区分“in-the-loop”和“finished”状态,由于我上面描述的原因,它不能从后者返回到前者。这可能就是它以这种方式实现的原因。

【讨论】:

  • 嗯 - 但如果你正在查看 StopIterator#result - 你知道你已经走到了尽头。不过我同意你的观点。我认为问题的一部分是如果下一个值未定义,#peek 会引发 StopIteration。这让我觉得有点奇怪,因为#peek 的想法是它向前看,而不增加。应该有一种方法可以在不炸毁枚举器的情况下检查您是否在最后。查看源代码,如果您位于枚举器的末尾,#peek 可能会引发不同的错误
  • @Steve,如果peek 没有引发异常(可以捕获哪个异常),它的返回值怎么会表明已经到达枚举数的末尾? nil 不能使用,因为它可能由枚举器产生。我想可以有一个Enumerator 方法end_reached?
  • 嗨 Cary,我认为微妙之处在于 StopIteration 可用于返回一个值 - 所以你不能使用 StopIteration 异常 - 它必须是一个不同的异常。我完全同意nil 的问题。 end_reached? 返回 nil 会更好,因为它不会引发异常(所有堆栈展开开销)
【解决方案2】:

实际上,我认为答案是自定义枚举器,如下所述:StackOverflow: enumerator cloning

这就是我最终得到的结果。 Initialize 需要一个块,该块可用于根据需要在末尾构造新元素。

   class ArrayEnumerator
     def initialize(array, &block)
       @ary = array
       @block = block
       @n = 0
     end

     def peek
       @block.call(@ary,@n) if @n == @ary.length
       @ary[@n]
     end

     def next
        v = peek
        @n += 1
        v
     end
   end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-23
    • 2014-12-21
    • 2015-07-16
    • 2013-03-18
    相关资源
    最近更新 更多