2013-12-23 29 views
2

我想爲Ruby 2的Enumerator :: Lazy類實現一個take_until方法。它的工作原理與take_while類似,但當代碼塊返回true時停止迭代。結果應該包括產生的塊匹配的項目。如何在Enumerator :: Lazy方法中停止迭代?

我的問題是我如何表示迭代的結束已達到?使用常規枚舉器時,可以在每個方法中引發StopIteration錯誤,以表示迭代器的結束。但是,這似乎並不適用於懶惰的枚舉:

class Enumerator::Lazy 
    def take_until 
    Lazy.new(self) do |yielder, *values| 
     yielder << values 
     raise StopIteration if yield *values 
    end 
    end 
end 

(1..Float::INFINITY).lazy.take_until{ |i| i == 5 }.force 

我也試圖突破塊無效。 The documentation for Enumerator::Lazy似乎也沒有幫助。

爲什麼使用take_while不是一個有效的選項。

take_while的主要問題是,從本質上講,它會嘗試評估比您需要的更多的物品。在我的應用程序中,枚舉器不會產生數字,而是通過網絡獲取消息。試圖評估一個不存在的信息(但是?)是一種非常不受歡迎的阻止行爲。這通過以下人爲示例來說明:

enum = Enumerator.new do |y| 
    5.times do |i| 
    y << i 
    end 
    sleep 
end 

enum.lazy.take_while{ |i| i < 5 }.force 

要從此枚舉器接收前五項,您將需要評估第六個結果。這並不像它那樣懶惰。在我的使用情況下,這是不可取的,因爲這個過程會被阻止。

提供枚舉::懶惰

標準庫包括take方法,做類似於我想要的東西純Ruby實現的take。它不會使用塊作爲條件,而是使用數字,但是一旦達到該數字,它就會跳出迭代,而不是評估另外一個項目。繼上面的例子:

enum.lazy.take(5).force 

這沒有得到第6項,所以不阻止。問題是標準庫中的版本是用C語言實現的,我似乎無法弄清楚這是如何在純Ruby中實現的。該方法的紅寶石實現將是一個可以接受的答案。

提前致謝!

+0

我討厭問,但不是你只是使用take_while並根據需要修改條件? –

+0

這是一個有效的問題,但我認爲答案是:不,我不能。我的使用案例涉及到一系列的響應,我特別想要在遇到某些情況時對序列進行分隔。 Take_while不會包含匹配的項目本身,而是將順序返回到第一個「未命中」。希望這是有道理的。 –

+1

所以'take_until'會否定一個'take_while',它會做一個額外的'yield next'? – steenslag

回答

0

每我的意見,記錯修改take_while是更好的選擇(或至少一個有效的):

(1..Float::INFINITY).lazy.take_while { |i| i < 6 }.force 
=> [1, 2, 3, 4, 5] 

對於更復雜的情況是不太容易改寫,添加一個變量:

found = false 
(1..Float::INFINITY).lazy.take_while do |i| 
    if i == 5 
    found = true 
    else 
    !found 
    end 
end.force 
=> [1, 2, 3, 4, 5] 

您還可以根據去年塊定義take_while,太:

class Enumerator::Lazy 
    def take_until 
    take_while do |*args| 
     if [email protected] 
     @found = yield(*args) 
     true 
     else 
     false 
     end 
    end 
    end 
end 

注意,它不會無謂地調用該塊,太:

p (1..20).lazy.take_until{|i| p i; i == 5}.force 
p (1..20).lazy.take_until{|i| p i; i == 3}.force 
p (1..20).lazy.take_until{|i| p i; i == 8}.force 
+0

'take_while'的主要問題在於,它的性質會嘗試評估比您需要的更多項目。在我的應用程序中,枚舉器不會產生數字,而是通過網絡獲取消息。試圖評估一個不存在的信息(但是?)是一種非常不受歡迎的阻止行爲。這就是爲什麼在我的情況下,take_while不是一個有效的選項。 –

+0

那麼,因此建議相應地重寫條件。 : - | (但是請稍後查看我編輯的答案。) –

+0

There ...使用'take_while'實現'take_until'實現,根據我的初始答案。或者我誤解了你的編輯? –

0

我剛剛發現這個實現。這不是最優的,因爲它會通過內部緩存結果而暗示強制迭代。

class Enumerator::Lazy 
    def take_until 
    if block_given? 
     ary = [] 
     while n = self.next 
     ary << n 
     if (yield n) == true 
      break 
     end 
     end 
     return ary.lazy 
    else 
     return self 
    end 
    end 
end 

使用從我關注的例子:

enum = Enumerator.new do |y| 
    5.times do |i| 
    y << i 
    end 
    sleep 
end 

p enum.lazy.take_until{ |i| i == 4 }.force 

現在返回[0, 1, 2, 3, 4]

我保持這個問題,開的時間長一點,看看是否有人想出了一個真正的懶惰實現,但我懷疑我們會找到一個。

1

這是一個古老的問題,但無論如何:正如你所說,你真正需要的是一個Lazy#take_until,當然Lazy#take_while將需要獲得下一個項目來決定是否打破。我一直無法執行Lazy#take_until使用Lazy#new { ... },顯然沒有破壞機制。這是一個可能的解決方法:

class Enumerator::Lazy 
    def take_until 
    Enumerator.new do |yielder| 
     each do |value| 
     yielder << value 
     break if yield(value) 
     end 
    end.lazy 
    end 
end 
+0

@ Marc-AndréLafortune:我想知道你對此的看法,必須有辦法打破懶惰的普查員。 – tokland