Ruby Fiber指南(一)基礎(chǔ)
Ruby Fiber指南(二)參數(shù)傳遞
Ruby Fiber指南(三)過(guò)濾器
Ruby Fiber指南(四)迭代器
Ruby Actor指南(五)實(shí)現(xiàn)Actor
上一節(jié)介紹了利用Fiber實(shí)現(xiàn)類unix管道風(fēng)格的過(guò)濾鏈,這一節(jié)將介紹利用Fiber來(lái)實(shí)現(xiàn)迭代器,
我們可以將循環(huán)的迭代器看作生產(chǎn)者-消費(fèi)者模式的特殊的例子。迭代函數(shù)產(chǎn)生值給循環(huán)體消費(fèi)。所以可以使用Fiber來(lái)實(shí)現(xiàn)迭代器。協(xié)程的一個(gè)關(guān)鍵特征是它可以不斷顛倒調(diào)用者與被調(diào)用者之間的關(guān)系,這樣我們毫無(wú)顧慮的使用它實(shí)現(xiàn)一個(gè)迭代器,而不用保存迭代函數(shù)返回的狀態(tài),也就是說(shuō)無(wú)需在迭代函數(shù)中保存狀態(tài),狀態(tài)的保存和恢復(fù)交由Fiber自動(dòng)管理。
這一節(jié)的介紹以一個(gè)例子貫穿前后,我們將不斷演化這個(gè)例子,直到得到一個(gè)比較優(yōu)雅的可重用的代碼,這個(gè)例子就是求數(shù)組的全排列。如數(shù)組[1,2,3]的全排列包括6種排列:
2 3 1
3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
全排列的遞歸算法實(shí)現(xiàn)很簡(jiǎn)單,我們用Ruby實(shí)現(xiàn)如下:
#全排列的遞歸實(shí)現(xiàn)
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)
算法的思路是這樣:將數(shù)組中的每一個(gè)元素放到最后,依次遞歸生成所有剩余元素的排列,沒(méi)完成一個(gè)排列就打印出來(lái)。很顯然,這里有消費(fèi)者和生產(chǎn)者的關(guān)系存在,生產(chǎn)者負(fù)責(zé)產(chǎn)生排列,消費(fèi)者負(fù)責(zé)打印任務(wù),整個(gè)程序由消費(fèi)者驅(qū)動(dòng),因此用Fiber改寫如下:
第一步,將打印任務(wù)修改為Fiber#yield,生產(chǎn)者產(chǎn)生一個(gè)排列后將結(jié)果傳遞給消費(fèi)者并讓出執(zhí)行權(quán):
def permgen (a, n)
if n == 0 then
Fiber.yield(a)
……
end
第二步,實(shí)現(xiàn)一個(gè)迭代器工廠,返回一個(gè)匿名的迭代函數(shù),迭代函數(shù)請(qǐng)求Fiber產(chǎn)生一個(gè)新的排列:
def perm(a)
f=Fiber.new do
permgen(a,a.size)
end
return lambda{ f.resume if f.alive? }
end
這樣一來(lái)我們就可以利用一個(gè)while循環(huán)來(lái)打印全排列:
it=perm([1,2,3,4])
while a=it.call
printResult(a)
end
注意到,在perm方法中有一個(gè)很常見(jiàn)的模式,就是將對(duì)Fiber的resume封裝在一個(gè)匿名函數(shù)內(nèi),在lua為了支持這種模式還特意提供了一個(gè)coroutine.wrap方法來(lái)方便編程,在Ruby Fiber中卻沒(méi)有,不過(guò)我們可以自己實(shí)現(xiàn)下,利用open-class的特性實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單:
#為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
Fiber#wrap方法跟new方法一樣,創(chuàng)建一個(gè)新的Fiber,但是返回的是一個(gè)匿名函數(shù),這個(gè)匿名函數(shù)負(fù)責(zé)去調(diào)用fiber的resume,利用wrap改寫perm方法變得更簡(jiǎn)潔:
def perm(a)
Fiber.wrap{ permgen(a,a.size) }
end
但是還不夠,while循環(huán)的方式還是不夠優(yōu)雅,每次都需要明確地調(diào)用迭代器的call方法,這一點(diǎn)讓人挺不舒坦,如果能像for...in那樣的泛型循環(huán)就好了,我們知道Ruby中的for...in其實(shí)是一個(gè)語(yǔ)法糖衣,都是轉(zhuǎn)變成調(diào)用集合的each方法并傳入處理的block,因此,要想實(shí)現(xiàn)一個(gè)優(yōu)雅的迭代器,我們做下封裝就好了:
class FiberIterator
def initialize
@fiber_wrap=Fiber.wrap do
yield
end
end
def each
while value=@fiber_wrap.call
yield value
end
end
end
那么現(xiàn)在的perm方法變成了創(chuàng)建一個(gè)迭代器FiberIterator:
def perm(a)
FiberIterator.new{ permgen(a,a.size) }
end
這樣一來(lái)我們就可以通過(guò)for...in來(lái)調(diào)用迭代器了
it=perm([1,2,3,4])
for a in it
printResult(a)
end