Ruby 程式設計/參考/物件/Enumerable
Enumerator 在 Ruby 中以 Enumerable::Enumerator 的形式出現在 1.8.x 中,而在 1.9.x 中則以 (僅) Enumerator 的形式出現。
Enumerator 有多種不同的使用方法
- 作為“each”的代理
- 作為塊中值的來源
- 作為外部迭代器
這是在 ruby 1.8 中引入的 Enumerator 的第一種使用方法。它解決了以下問題:Enumerable 方法如 #map 和 #select 會在你的物件上呼叫 #each,但如果你想使用其他方法(如 #each_byte 或 #each_with_index)進行迭代怎麼辦?
Enumerator 是一個簡單的代理物件,它接收對 #each 的呼叫並將它重定向到基礎物件上的不同方法。
require 'enumerator' # needed in ruby <= 1.8.6 only
src = "hello"
puts src.enum_for(:each_byte).map { |b| "%02x" % b }.join(" ")
對 'enum_for'(或等效地 'to_enum')的呼叫建立了 Enumerator 代理。它是以下內容的簡寫
newsrc = Enumerable::Enumerator.new(src, :each_byte)
puts newsrc.map { |b| "%02x" % b }.join(" ")
在 ruby 1.9 中,Enumerable::Enumerator已更改為Enumerator
在 ruby 1.9 中,Enumerator.new 可以接收一個塊,該塊在呼叫 #each 時執行,並直接產生值。
block = Enumerator.new {|g| g.yield 1; g.yield 2; g.yield 3}
block.each do |item|
puts item
end
“g << 1”是“g.yield 1”的替代語法
沒有使用 Fiber 或 Continuation 等高階語言功能,這種形式的 Enumerator 很容易移植到 ruby 1.8
它與建立產生值的自定義物件非常相似
block = Object.new def block.each yield 1; yield 2; yield 3 end block.each do |item| puts item end
但是,它也為後面將要描述的可列舉物件的“延遲”評估奠定了基礎。
ruby 1.9 還允許你反轉 Enumerator,使其成為值的“拉取”源,有時也稱為“外部迭代”。仔細觀察它與上一個例子的區別
block = Enumerator.new {|g| g.yield 1; g.yield 2; g.yield 3}
while item = block.next
puts item
end
控制流來回切換,第一次呼叫 #next 時會建立一個 Fiber,它儲存呼叫之間的狀態。因此,它的效率低於直接使用 #each 進行迭代。
當你呼叫 #next 且沒有更多值時,會丟擲一個StopIteration異常。這會被 while 迴圈靜默捕獲。StopIteration是IndexError的子類,而IndexError.
又是
require 'generator'
block = Generator.new {|g| g.yield 1; g.yield 2; g.yield 3}
while block.next?
puts block.next
end
StandardError
的子類ruby 1.8 中最接近的等效功能是 Generator,它是使用 Continuations 實現的。
延遲評估
Enumerator.new do |y|
source.each do |input| # filter INPUT
...
y.yield output # filter OUTPUT
end
end
class Enumerator
def defer(&blk)
self.class.new do |y|
each do |*input|
blk.call(y, *input)
end
end
end
end
在帶有塊的 Enumerator 中,要產生的目標會作為顯式引數傳遞。這使得有可能設定方法呼叫的鏈,以便每個值從左到右傳遞到整個鏈,而不是在每一步都構建中間值陣列。
res = (1..1_000_000_000).to_enum.
defer { |out,inp| out.yield inp if inp % 2 == 0 }. # like select
defer { |out,inp| out.yield inp+100 }. # like map
take(10)
p res
基本模式是帶有塊的 Enumerator,它處理輸入值併為每個輸入值產生(零個或多個)輸出值。
所以,讓我們把它包裝在一個便利方法中
這個新的方法 'defer' 可以用作 select 和 map 的“延遲”形式。它不會構建值陣列並在最後返回該陣列,而是立即產生每個值。這意味著你可以更快地獲得答案,並且它可以處理巨大甚至無限長的列表。示例
雖然我們從一個包含十億個專案的列表開始,但在最後我們只使用了生成的第一個 10 個值,因此我們在此完成後停止迭代。你可以在 ruby 1.8 中使用相同的功能,使用facets 庫。為了方便起見,它還提供了一個Denumberable 模組,其中包含常見 Enumerable 方法(如 map、select 和 reject)的延遲版本。
>> a = ["foo","bar","baz"]
=> ["foo", "bar", "baz"]
>> b = a.each_with_index
=> #<Enumerable::Enumerator:0xb7d7cadc>
>> b.each { |args| p args }
["foo", 0]
["bar", 1]
["baz", 2]
=> ["foo", "bar", "baz"]
>>
返回 Enumerators 的方法[編輯 | 編輯原始碼]從 1.8.7 開始,許多 Enumerable 方法如果未提供塊,將返回 Enumerator。
src = "hello"
puts src.each_byte.map { |b| "%02x" % b }.join(" ")
這意味著通常你不需要顯式呼叫
=> ["foo", "bar", "baz"]
>> b = a.select
=> #<Enumerable::Enumerator:0xb7d6cfb0>
>> b.each { |arg| arg < "c" }
=> ["bar", "baz"]
>>
enum_for
本頁面上的第一個示例簡化為- 這會導致非對映類方法出現一些奇怪的行為——當你稍後在物件上呼叫 #each 時,你必須為其提供“正確型別”的塊。
- 更多 Enumerator 閱讀
- [編輯 | 編輯原始碼]
工單 707
enum_for 在 Strictly Untyped 部落格中Generator 在 Anthony Lewis 的部落格中
array = ['Superman','Batman','The Hulk']
array.each_with_index do |item,index|
puts "#{index} -> #{item}"
end
# will print
# 0 -> Superman
# 1 -> Batman
# 2 -> The Hulk
each_with_index 會使用項及其索引呼叫其塊。
range = 1 .. 10
# find the even numbers
array = range.find_all { |item| item % 2 == 0 }
# returns [2,4,6,8,10]
array = ['Superman','Batman','Catwoman','Wonder Woman']
array = array.find_all { |item| item =~ /woman/ }
# returns ['Catwoman','Wonder Woman']