Lua以协同程序实现迭代器

用作迭代器的协同

我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子。迭代函数产生值给循环体消费。所以可以使用协同来实现迭代器。协同的一个关键特征是它可以不断颠倒调用者与被调用者之间的关系,这样我们毫无顾虑的使用它实现一个迭代器,而不用保存迭代函数返回的状态。

我们来完成一个打印一个数组元素的所有的排列来阐明这种应用。直接写这样一个迭代函数来完成这个任务并不容易,但是写一个生成所有排列的递归函数并不难。思路是这样的:将数组中的每一个元素放到最后,依次递归生成所有剩余元素的排列。代码如下:

function permgen (a, n) 
if n == 0 then
 printResult(a) 
else 
 for i=1,n do
 -- put i-th element as the last one 
 a[n], a[i] = a[i], a[n] 
 -- generate all permutations of the other elements 
 permgen(a, n - 1) 
 -- restore i-th element 
 a[n], a[i] = a[i], a[n] 
 end 
end 
end 
function printResult (a) 
for i,v in ipairs(a) do
 io.write(v, " ") 
end 
 io.write("\n") 
end 
permgen ({1,2,3,4}, 4) 

有了上面的生成器后,下面我们将这个例子修改一下使其转换成一个迭代函数:

1. 第一步 printResult 改为 yield 
function permgen (a, n) 
if n == 0 then
 coroutine.yield(a) 
else 
 ... 
 
2. 第二步,我们定义一个迭代工厂,修改生成器在生成器内创建迭代函数,并使生成器运行在一个协同程序内。
3. 迭代函数负责请求协同产生下一个可能的排列。
function perm (a) 
local n = table.getn(a) 
local co = coroutine.create(function () permgen(a, n) end) 
return function () -- iterator 
 local code, res = coroutine.resume(co) 
 return res 
end 
end 
这样我们就可以使用 for 循环来打印出一个数组的所有排列情况了:
for p in perm{"a", "b", "c"} do
 printResult(p) 
end 
--> b c a 
--> c b a 
--> c a b 
--> a c b 
--> b a c 
--> a b c 

perm 函数使用了 Lua 中常用的模式:将一个对协同的 resume 的调用封装在一个函数内部,这种方式在 Lua 非常常见,所以 Lua 专门为此专门提供了一个函数coroutine.wrap。与 create 相同的是,wrap 创建一个协同程序;不同的是 wrap 不返回协同本身,而是返回一个函数,当这个函数被调用时将 resume 协同。wrap 中 resume 协同的时候不会返回错误代码作为第一个返回结果,一旦有错误发生,将抛出错误。我们可以使用 wrap 重写 perm:

function perm (a) 
local n = table.getn(a) 
return coroutine.wrap(function () permgen(a, n) end) 
end 

一般情况下,coroutine.wrap 比 coroutine.create 使用起来简单直观,前者更确切的提供了我们所需要的:一个可以 resume 协同的函数,然而缺少灵活性,没有办法知道 wrap所创建的协同的状态,也没有办法检查错误的发生。

你可能感兴趣的:(Lua以协同程序实现迭代器)