2010-02-17 54 views
7

在C#中,你可以做這樣的事情:紅寶石相當於C#的‘產量’關鍵字,或者創建序列沒有預先分配存儲

public IEnumerable<T> GetItems<T>() 
{ 
    for (int i=0; i<10000000; i++) { 
     yield return i; 
    } 
} 

這將返回1000個整數的枚舉序列而沒有分配集合在這段長度的記憶中。

有沒有辦法在Ruby中做同樣的事情?我試圖處理的具體示例是將矩形數組展平爲要列舉的值序列。返回值不一定是ArraySet,而是某種只能按順序迭代/枚舉的序列,而不是索引。因此,整個序列不需要同時分配在內存中。在.NET中,這是IEnumerableIEnumerable<T>

因爲我對.NET術語更熟悉,所以在Ruby世界中使用的術語的任何說明都會有所幫助。

編輯

也許我原來的問題是不是真的足夠清晰的 - 我認爲yield在C#中非常不同的含義和Ruby是混亂的原因這裏的事實。

我不想要一個解決方案,需要我的方法來使用塊。我想要一個具有實際返回值的解決方案。返回值可以方便地處理序列(過濾,投影,連接,壓縮等)。

這裏是get_items的我怎麼可能用一個簡單的例子:

things = obj.get_items.select { |i| !i.thing.nil? }.map { |i| i.thing } 

在C#中,任何方法返回IEnumerable使用一個yield return使編譯器生成迎合這種行爲幕後有限狀態機。我懷疑Ruby的延續可能會實現類似的效果,但我還沒有看到過一個例子,而且我不清楚自己會如何做到這一點。

確實似乎有可能使用Enumerable來實現此目的。一個簡單的解決方案是給我們一個Array(其中包括模塊Enumerable),但我不想在內存中創建一個包含N個項目的中間集合,因爲它可能只是懶惰地提供它們,並且完全避免任何內存高峯。

如果這仍然沒有意義,那麼考慮上面的代碼示例。 get_items返回一個枚舉,調用select。傳遞給select的是一個知道如何在需要時提供序列中下一個項目的實例。重要的是,整個項目的收集尚未計算。只有當select需要一個項目時,它會要求它,get_items中的潛在代碼將啓動並提供它。這個懶惰攜帶鏈,這樣select只有在map要求它時從序列中提取下一項。因此,一次可以對一個數據項執行長鏈操作。實際上,以這種方式構造的代碼甚至可以處理無限的數值序列,而不會有任何種類的內存錯誤。

所以,這種懶惰很容易用C#編碼,我不知道如何在Ruby中做到這一點。

我希望更清晰(我會盡量避免寫的問題在凌晨3點以後。)

回答

14

它是由Enumerator因爲Ruby 1.9的支持(和回移植到1.8.7)。見Generator: Ruby

陳詞濫調例如:

fib = Enumerator.new do |y| 
    y.yield i = 0 
    y.yield j = 1 
    while true 
    k = i + j 
    y.yield k 
    i = j 
    j = k 
    end 
end 

100.times { puts fib.next() } 
+0

@Matthew,這看起來正是我想要的。太糟糕了,它是Ruby 1.9,因爲我目前在1.8.7。將看看我是否可以升級。如果你知道1.9以前的方法,我想聽聽它。 –

+1

根據這篇文章http://www.rubyinside.com/ruby-187-released-912.html的'Enumerator'序列的支持已經回移植到1.8.7。快樂的時光。 –

1

而不必太多紅寶石的經驗,在yield return做什麼C#通常被稱爲懶惰評價懶執行:提供只需要他們的答案。這不是關於分配內存,而是關於延遲計算直到實際需要,以類似於簡單線性執行(而不是基本迭代器與狀態保存)的方式表示。

快速谷歌在公測開啓了ruby library。看看它是你想要的。

+1

有人請糾正我,如果我錯了,但我相信Enumerator提供惰性執行,無論如何? – Shadowfirebird

5

你具體的例子是相當於10000000.times,但讓我們假設針對該次方法並不存在的時刻,你想實現它自己,它會是這樣的:

class Integer 
    def my_times 
    return enum_for(:my_times) unless block_given? 
    i=0 
    while i<self 
     yield i 
     i += 1 
    end 
    end 
end 

10000.my_times # Returns an Enumerable which will let 
       # you iterate of the numbers from 0 to 10000 (exclusive) 

編輯:爲了澄清我的答案:

在上面的例子中,my_times可以(而且)在沒有塊的情況下使用,並且它會返回一個Enumerable對象,它可以讓你遍歷從0到n的數字。所以它和你在C#中的例子完全相同。

這個工程使用enum_for方法。 enum_for方法以其參數作爲方法的名稱,這會產生一些項目。然後它返回一個Enumerator類(它包含Enumerable模塊)的一個實例,當它迭代時將執行給定的方法,併爲您提供該方法產生的項目。請注意,如果你只在枚舉的第一個X項目迭代,該方法將只(即只之多必要的方法將被執行)執行,直到X項目已產生了,如果你遍歷枚舉兩次,方法將被執行兩次。

在1.8.7+它已成爲定義的方法,其收益率的項目,這樣,當不使用塊調用時,他們會返回一個枚舉這將讓在這些項目懶洋洋用戶迭代。這是通過將return enum_for(:name_of_this_method) unless block_given?行添加到方法的開頭來完成的,就像我在我的例子中所做的那樣。

+0

此答案需要一個塊。在C#中沒有塊的概念,而C#中的yield#語句做了非常不同的事情。有沒有辦法創建一個任意序列作爲方法的返回值?把它作爲一個實例的好處是它可以被操縱,過濾,連接,映射等...... –

+0

我已經更新了我的問題,以便更明確。我認爲語言之間'yeild'關鍵字的意義不同引起了一些混淆。 –

+0

@德魯:「這個答案需要一個塊。」不,它沒有。看看我的示例用法 - 沒有塊。我可以通過'10000.my_times.first'來獲得0(枚舉器的第一個元素)或'10000.my_times.to_a'來獲取枚舉器內容的數組。或者我可以調用任何其他Enumerable方法。 my_times(沒有塊)返回一個Enumerable,其中包含了所有項目。這正是你所要求的。 – sepp2k

-2

C#撕開了「產量」關鍵字右出Ruby-的看到Implementing Iterators here更多。

至於你的實際問題,你有可能是一個數組的數組,你想在列表上的完整長度來創建一個單向迭代?也許值得看看array.flatten作爲一個起點 - 如果性能沒有問題,那麼你可能不需要進一步太多。

+3

不太可能。 C#2.0規範於2002年12月完成。Ruby 1.9.0於2007年12月發佈。此外,如果C#從某處「扯下」它,那是CLU,它可追溯到1975年。 –

+1

@Matthew Flaschen:Ruby自從90年代。它在1.9中沒有被引入。但是,它與C#完全不同,即使它們都與迭代有關。 Ruby的yield只是調用傳遞塊的糖,而C#關鍵字本身返回一個迭代器。因此,例如,「迭代器」文檔('threeTimes')中的第一個例子不會在C#中使用yield來實現。 C#版本似乎來自Python。 – Chuck

+1

啊,好吧。我誤解了維基百科的文章。但我的主要觀點是,說C#從Ruby拿走yield關鍵字是不合理的,因爲CLU的產量在數十年前就會超過它們。 –