Ruby Enumerator和Enumerable interaction:StopIterator依赖项

时间:2017-09-28 09:29:08

标签: ruby enumerator

我刚刚遇到一些有趣的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)。

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

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

例如,在您要枚举的约会日记中查找第一个免费日。这自然是一个数组而不是一个哈希(因为它是有序的),但是在迭代它时很难扩展。

思考?

2 个答案:

答案 0 :(得分:3)

我认为原因是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

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

由于我上面描述的原因,枚举器必须区分“在环”和“完成”状态,并且它不能从后者返回到前者。这可能就是为什么以这种方式实施的原因。

答案 1 :(得分:-1)

实际上,我认为答案是自定义枚举器,如下所述: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