7.1 迭代器与closure
所谓迭代器就是一种可以遍历一种集合中所有元素的机制;在Lua中,通常将迭代器表示为函数;
每调用一次函数即返回集合中的下一个元素;
closure(闭合函数)对于迭代器的实现提供了极佳的支持;一个closure结构通常涉及到两个函数
:closure本身和一个用于创建该closure的工厂(factory)函数;
简单的迭代器示例 ——
function values(t)
local i = 0
returnfunction() i = i + 1; return t[i] end
end
在本例中values就是一个工厂,每当调用这个工厂是,它就创建一个新的closure(迭代器本身),
这个closure将它的状态保存在其外部变量t和i中。
每当调用这个迭代器时,它就从列表t中返回下一个值,直到最后一个元素返回后,迭代器就会返回nil。
以此表示迭代的结束。
在while循环中使用这个迭代器:
t = {10,20, 30}
iter = values(t)
while true do
local element= iter()
if element ==nil then break end
print(element)
end
使用泛型for则更为简单:
for element in values(t) do
print(element)
end
泛型for在内部保存了迭代器函数,因此不再需要iter变量;它在每次新迭代时调用迭代器,
并在迭代器返回nil时结束循环;
高级示例allwords——遍历当前输入文件中所有单词的迭代器。为了完成这种迭代,需要保持
两个值:当前行的内容(变量line)及在该行中所处的位置(变量pos);
function allwords()
local line =io.read()
local pos = 1
return function()
while line do
local s, e = string.find(line,"%w+",pos) --查找任意单词
if s then
pos = e + 1
return string.sub(line, s, e) --返回该单词
else
line =io.read() --没有找到单词,尝试下一行
pos = 1
end
end
return nil
end
end
7.2 泛型for的语义
上述迭代器都有一个缺点,就是需要为每个新的循环创建一个新的closure。在一些
情况下这样的开销可能不容易接受。可以通过泛型for来解决。
泛型for在循环过程中保存着3个值: 一个迭代器函数,一个恒定状态(invariant state)
和一个控制变量(control varible)。泛型for的语法如下:
for
end
在循环过程中如果它为nil时循环就结束。
for做的第一件事情就是对in后面的表达式求值。表达式应该返回3个值供for保存:
迭代器函数、恒定状态和控制变量的初值。
之后for会以恒定状态和控制变量来调用迭代器函数。然后for将迭代器函数的返回值赋予变量列表中变量。
如果第一个返回值为nil(即赋予控制变量的那个值),循环终止。否则,for执行它的循环体,随后再次调用
迭代器函数,并重复这个过程。更明确地说以下语句:
for var1, ..., varn in
等价于以下代码:
do
local f, s,var =
while true do
localvar1,..., varn = f(s, var)
var =var1
if var ==nil then break end
end
end
7.3无状态的迭代器
所谓的无状态的迭代器就是一种自身不保存任何状态的迭代器。我们可以在多个循环中使用同一个
无状态的迭代器,这样可以避免创建新的closure开销。
在每次迭代中,for循环都会用恒定状态和控制变量来调用迭代器函数。
一个无状态的迭代器可以根据这两个值为下次迭代生成下一个元素。
这类迭代器典型的例子就是ipairs.如代码:
a = {"one","two","three"}
local function iter(a, i)
i = i + 1
local v =a[i]
if v then
return i,v
end
end
function ipairs(a)
return iter,a, 0
end
for i, v in ipairs(a) do
print(i, v)
end
当Lua调用for循环中的ipairs(a)时,它会获得3个值:迭代器函数iter、恒定状态a和控制变量的初值。
然后Lua调用iter(a,0)得到1,a[1]。在第二次迭代中,继续调用iter(a,1)得到2,a[2]。依次类推,
直至得到的第一个元素为nil时终止循环。
函数pairs与ipairs类似;不同的是它的迭代器函数是Lua中的一个基本函数next.
function pairs(t)
return next, t, nil
end
调用next(t,nil)时返回table的第一组值;调用next(t,k)时,k是table(t)中的一个key,此调用会以table中
的任意次序返回一组值:此table的下一个key,及这个key所对应的值。若没有下一组值时,next返回nil。
关于无状态迭代器的另外一个例子是一种可以遍历链表的迭代器.
local function getnext(list,node)
if not node then
returnlist
else
return node.next
end
end
function traverse(list)
returngetnext, list, nil
end
这里将链表的头结点作为恒定状态(traver的第二个返回值),将当前结点作为控制变量。当第一次调用迭代器函数getnext时,node为nil,因此返回list作为第一个结点。在后续调用中node就不在为nil了,所以返回node.next。
使用:
list = nil
for line in io.lines() do
list = {val =line, next = list}
end
for node in traverse(list) do
print(node.val)
end
7.4具有复杂状态的迭代器
通常迭代器需要保存许多状态,可是泛型for却只提供了一个恒定状态和一个控制变量用于状态的保存。
解决方法:一是使用closure;二是将迭代器所需的所有状态打包为一个table,保存在恒定状态中。
在循环过程中恒定状态总是同一个table,但这个table的内容是可以改变的。
示例 —— 重写allwords迭代器,这个迭代器遍历当前输入文件中的所有单词,这次将它的状态保存到
一个table中,这个table具有两个字段:line 和 pos。
function iterator(state)
whilestate.line do
local s,e = string.find(state.line, "%w+", state.pos)
if s then
state.pos =e + 1
return string.sub(state.line, s, e)
else
state.line= io.read()
state.pos= 1
end
end
return nil
end
function allwords()
local state ={line = io.read(),pos = 1}
returniterator, state
end
建议:
尽可能地编写无状态的迭代器,那些迭代器将所有的状态保存在for变量中,不需要在开始
一个循环时创建任何新的对象。
如果迭代器无法套用这个模型,那么就应该使用closure。closure显得更加优雅一点,通常基于
一个closure实现的迭代器会比一个使用table的迭代器更为高效。因为创建一个closure比创建
一个table更廉价,其次访问"非局部的变量"也比访问table字段更快。
7.5真正的迭代器
迭代器并没有做实际的迭代,真正做迭代的是for循环。而迭代器只是为每次迭代提供一些成功
后的返回值。准确地说应该称其为"生成器".
一种创建迭代器的方式就是,迭代器接受一个函数作为参数,并在其内部的循环中调用这个函数
—— 示例,重写allwords:
function allwords(f)
for line in io.lines()do
for wordin string.gmatch(line, "%w+") do
f(word) -- 调用函数参数
end
end
end
应用: allwords(print)
通常还可以使用一个匿名函数作为循环体,以下代码计算了单词"hello"在输入文件中出现的次数:
local count = 0
allwords(function(w)
if w =="hello" then count = count + 1 end
end)
print(count)
“真正的迭代器”在老版本的Lua中非常流行,与“生成器风格的迭代器”相比,这两种风格都有相同
的开销,即每次迭代都有一次函数调用。
不过“生成器风格的迭代器”更加灵活,这种灵活性体现在两方面,首先它允许两个或多个
并行的迭代过程,其次它允许在迭代体中使用break和return语句。对于正在的迭代器来说,
return语句只能从匿名函数中返回,并不能从做迭代的函数中返回。