我刚刚遇到一些有趣的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,其块产生散列和键)。因此,这使得构建一个可以在枚举时扩展的数组非常难看和困难。
例如,在您要枚举的约会日记中查找第一个免费日。这自然是一个数组而不是一个哈希(因为它是有序的),但是在迭代它时很难扩展。
思考?
答案 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