Ruby Fiber指南(四)迭代器
Posted on 2010-03-12 12:48 dennis 閱讀(3285) 評論(0) 編輯 收藏 所屬分類: 動態語言 、my open-sourceRuby Fiber指南(一)基礎
Ruby Fiber指南(二)參數傳遞
Ruby Fiber指南(三)過濾器
Ruby Fiber指南(四)迭代器
Ruby Actor指南(五)實現Actor
上一節介紹了利用Fiber實現類unix管道風格的過濾鏈,這一節將介紹利用Fiber來實現迭代器,我們可以將循環的迭代器看作生產者-消費者模式的特殊的例子。迭代函數產生值給循環體消費。所以可以使用Fiber來實現迭代器。協程的一個關鍵特征是它可以不斷顛倒調用者與被調用者之間的關系,這樣我們毫無顧慮的使用它實現一個迭代器,而不用保存迭代函數返回的狀態,也就是說無需在迭代函數中保存狀態,狀態的保存和恢復交由Fiber自動管理。
這一節的介紹以一個例子貫穿前后,我們將不斷演化這個例子,直到得到一個比較優雅的可重用的代碼,這個例子就是求數組的全排列。如數組[1,2,3]的全排列包括6種排列:
2 3 1
3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
全排列的遞歸算法實現很簡單,我們用Ruby實現如下:3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
#全排列的遞歸實現
def permgen (a, n)
if n == 0 then
printResult(a)
else
n.times do |i|
a[n-1], a[i] = a[i], a[n-1]
permgen(a, n - 1)
a[n-1], a[i] = a[i], a[n-1]
end
end
end
def printResult (a)
puts a.join(" ")
end
permgen([1,2,3,4],4)
算法的思路是這樣:將數組中的每一個元素放到最后,依次遞歸生成所有剩余元素的排列,沒完成一個排列就打印出來。很顯然,這里有消費者和生產者的關系存在,生產者負責產生排列,消費者負責打印任務,整個程序由消費者驅動,因此用Fiber改寫如下:def permgen (a, n)
if n == 0 then
printResult(a)
else
n.times do |i|
a[n-1], a[i] = a[i], a[n-1]
permgen(a, n - 1)
a[n-1], a[i] = a[i], a[n-1]
end
end
end
def printResult (a)
puts a.join(" ")
end
permgen([1,2,3,4],4)
第一步,將打印任務修改為Fiber#yield,生產者產生一個排列后將結果傳遞給消費者并讓出執行權:
def permgen (a, n)
if n == 0 then
Fiber.yield(a)
……
end
if n == 0 then
Fiber.yield(a)
……
end
第二步,實現一個迭代器工廠,返回一個匿名的迭代函數,迭代函數請求Fiber產生一個新的排列:
def perm(a)
f=Fiber.new do
permgen(a,a.size)
end
return lambda{ f.resume if f.alive? }
end
f=Fiber.new do
permgen(a,a.size)
end
return lambda{ f.resume if f.alive? }
end
這樣一來我們就可以利用一個while循環來打印全排列:
it=perm([1,2,3,4])
while a=it.call
printResult(a)
end
while a=it.call
printResult(a)
end
注意到,在perm方法中有一個很常見的模式,就是將對Fiber的resume封裝在一個匿名函數內,在lua為了支持這種模式還特意提供了一個coroutine.wrap方法來方便編程,在Ruby Fiber中卻沒有,不過我們可以自己實現下,利用open-class的特性實現起來非常簡單:
#為Fiber添加wrap方法
class Fiber
def self.wrap
if block_given?
f=Fiber.new do |*args|
yield *args
end
return lambda{|*args| f.resume(*args) if f.alive? }
end
end
end
class Fiber
def self.wrap
if block_given?
f=Fiber.new do |*args|
yield *args
end
return lambda{|*args| f.resume(*args) if f.alive? }
end
end
end
Fiber#wrap方法跟new方法一樣,創建一個新的Fiber,但是返回的是一個匿名函數,這個匿名函數負責去調用fiber的resume,利用wrap改寫perm方法變得更簡潔:
def perm(a)
Fiber.wrap{ permgen(a,a.size) }
end
Fiber.wrap{ permgen(a,a.size) }
end
但是還不夠,while循環的方式還是不夠優雅,每次都需要明確地調用迭代器的call方法,這一點讓人挺不舒坦,如果能像for...in那樣的泛型循環就好了,我們知道Ruby中的for...in其實是一個語法糖衣,都是轉變成調用集合的each方法并傳入處理的block,因此,要想實現一個優雅的迭代器,我們做下封裝就好了:
class FiberIterator
def initialize
@fiber_wrap=Fiber.wrap do
yield
end
end
def each
while value=@fiber_wrap.call
yield value
end
end
end
def initialize
@fiber_wrap=Fiber.wrap do
yield
end
end
def each
while value=@fiber_wrap.call
yield value
end
end
end
那么現在的perm方法變成了創建一個迭代器FiberIterator:
def perm(a)
FiberIterator.new{ permgen(a,a.size) }
end
這樣一來我們就可以通過for...in來調用迭代器了FiberIterator.new{ permgen(a,a.size) }
end
it=perm([1,2,3,4])
for a in it
printResult(a)
end
for a in it
printResult(a)
end