2017-09-28 64 views
1

我剛剛遇到一些有趣的枚舉行爲。 Enumerator中的位置似乎存在一些依賴關係 - 一旦您窺視了Enumerable的結尾並且引發了StopIteration,Enumerator就不會注意到Enumerable的擴展。Ruby Enumerator和可枚舉交互:StopIterator依賴

兩個例子說明:

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 

OK,到目前爲止,一切都很好。但是這個怎麼樣。讓我們定義一個方法來擴展陣列,當我們打到最後:

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,枚舉器將不會再次檢查是否數組(我猜一般,Enumerable)已被擴展。

這是預期的行爲?一個錯誤?一個特徵?

爲什麼你想要這樣做。好吧 - 通過使用散列,您可以通過傳遞Hash::new一個塊來設置默認值 - 並且您可以將塊傳遞給Array::new。但Array::new作爲參數的塊只有索引作爲鍵,而不是數組和索引(如Hash :: new,其塊將生成散列和鍵)。所以這使得構建一個可以在枚舉數組時通過擴展的數組變得非常難看和困難。

例如,圖像約會日記,你想枚舉通過找到第一個自由的一天。這自然是一個數組,而不是一個哈希(按照它的順序),但是在遍歷時很難擴展。

想法?

+0

恕我直言,你應該避免更新數組,同時迭代它以避免不一致。在你的例子中,你可以使用'find'或'find_index'來代替使用枚舉器。 – sschmeck

回答

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 

一旦拋出異常(創建,)的值應該是已知的(這是順便說一句6在第一種情況下,在第二[1,2,3]和在第三個100。),基本意思是允許重新進入循環會引入不一致性(值存在,但不再正確)。

枚舉器必須區分「in-the-the-loop」狀態和「finished」狀態,並且它不能從後者由於上述原因而屬於前者。這可能就是爲什麼它以這種方式實施的原因。

+0

嗯 - 但是如果您正在查看StopIterator#結果 - 您知道您已達到結尾。不過,我明白你的觀點。 我認爲問題的一部分是如果下一個值未定義,#peek會引發StopIteration。這讓我覺得有點奇怪,因爲#peek的想法是它展望未來,而不會增加。應該有一種方法來檢查你是否最終沒有炸燬統計員。 查看源代碼,如果你在Enumerator的末尾,也許#peek應該引發一個不同的錯誤 –

+0

@Steve,如果'peek'沒有引發異常(哪個可以捕獲),它怎麼可能返回值表明統計員的末端已經到達?無法使用'nil',因爲它可能是由統計員生成的。我想可以有一個'Enumerator'方法'end_reached?'。 –

+0

您好卡里,我認爲微妙的是,StopIteration可以用來返回一個值 - 所以你不能使用StopIteration異常 - 它必須是一個不同的例外。我完全同意'無'問題。 'end_reached?'返回'nil'會更好,因爲它不會引發異常(所有的堆棧展開開銷) –

-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