我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子。一个迭代函数会产生一些值,而循环体会消费这些内容。所以可以使用协同程序也可以用来实现迭代器。的确,协同程序为实现这类任务提供一个非常有用的工具,即协同的一个关键特征是它可以不断修改调用者与被调用者之间的关系,这样我们毫无顾虑的使用它实现一个迭代器,而不用保存迭代函数返回的状态信息。
我们来完成一个打印一个数组元素的所有的排列来阐明这种应用。直接写这样一个迭代函数来完成这个任务并不容易,但是写一个生成所有排列的递归函数并不难。思路是这样的:将数组中的每一个元素放到最后,依次递归生成所有剩余元素的排列。代码如下:
function permgen(a, n)
n = n or #a -- 默认n为a的大小
if n <= 1 then -- 还需要改变吗?
printResult(a)
else
for i=1,n do
a[n], a[i] = a[i], a[n] -- 将第一个元素放到数组末尾
permgen(a, n-1) -- 生成其余元素的排列
a[n], a[i] = a[i], a[n] -- 恢复第i个元素
end
end
end
function printResult (a)
for i=1,#a do
io.write(a[i], " ")
end
io.write("\n")
end
permgen ({1,2,3,4})
当生成函数完成后,将其转换为一个迭代器就非常容易了。首先,将printResult改为yield:
function permgen (a, n)
n = n or #a
if n <= 1 then
coroutine.yield(a)
else
<as before>
然后,定义一个工厂方法,用于将生成函数放到一个协同程序中运行,并创建迭代器函数。迭代器指示简单地唤醒协同程序,让其产生下一种排列:
function permutations (a)
local co = coroutine.create(function () permgen(a) end)
return function () -- 迭代器
local code, res = coroutine.resume(co)
return res
end
end
有了上面的函数,在for语句中遍历一个数组中的所有排列就非常简单了:
for p in permutations {"a", "b", "c"} do
printResult(p)
end
----------------------------------------------
function printResult (a)
for i=1,#a do
io.write(a[i], " ")
end
io.write("\n")
end
function permgen (a, n)
n = n or #a
if n <= 1 then
coroutine.yield(a)
else
for i=1,n do
a[n], a[i] = a[i], a[n] -- 将第一个元素放到数组末尾
permgen(a, n-1) -- 生成其余元素的排列
a[n], a[i] = a[i], a[n] -- 恢复第i个元素
end
end
end
function permutations (a)
local co = coroutine.create(function () permgen(a) end)
return function () -- 迭代器
local code, res = coroutine.resume(co)
return res
end
end
for p in permutations {"a", "b", "c"} do
printResult(p)
end
------------------------------------------------
permutations函数使用了一种在Lua中比较常见的模式,就是将一条唤醒协同程序的调用包装在一个函数中。由于这种模式比较常见,所以Lua专门提供了一个函数coroutine.wrap来完成这个功能。类似于create,wrap创建了一个新的协同程序。但不同的是,wrap并不是返回协同程序本身,而是返回一个函数。每当调用这个函数,即可唤醒一次协同程序。但这个函数与resume的不同之处在于,它不会返回错误代码。当遇到错误时,它会引发错误。若使用wrap,可以这么写permutations:
function permutations (a)
return coroutine.wrap(function () permgen(a) end)
end
通常,coroutine.wrap比couroutine.create更易于使用。它提供了一个对于协同程序编程实际所需的功能,即一个可以唤醒协同程序的函数。但也缺乏灵活性。无法检查wrap所创建的协同程序的状态,此外,也无法检测出运行时的错误。