首先让我们就一个定义达成一致:(在 Lua 中)迭代器是function-类似对象,每次调用时都会返回序列中的下一个值。我认为重写有帮助for
迭代完成是Lua参考手册:
for itemlist in expression do block end
逻辑上等价于(伪代码):
do
local func, seq, controlVar = expression
while true do
local itemlist = func(seq, controlVar)
if first of itemlist == nil then break end
controlVar = first of itemlist
block (which uses items in itemlist)
end
end
where expression
是数据的三元组(或返回这样的三元组的函数调用):
-
func
是实际的迭代器函数
-
seq
是被迭代的序列
-
controlVar
是循环控制变量
迭代状态是在迭代序列中查找下一项所需的任何状态。因此,无状态迭代器是这样一种迭代器:func
不包含任何此类状态:您可以调用func(seq, controlVar)
在任何时候,返回值总是相同的(如果 seq 没有改变);它不取决于通话之前发生的事情。
如上所示,Lua支持one循环控制变量。因此,为了使序列能够通过无状态迭代器进行迭代,必须能够根据以下条件确定序列中的下一项:one循环控制变量。即,必须能够仅从“(s,controlVar)”中找出“下一个项目”。这ipairs()
生成一个迭代器来执行以下操作:ipairs(s)
返回三元组(iterFunction, s, 0)
; the iterFunction
可以给予s
和索引 0,并返回1, s[1]
, then 2, s[2]
等等(最终对于包含 N 个项目的表来说什么也没有)。
如果查找序列中的下一项需要多个循环控制变量怎么办?或者取决于其他变量的状态,哪些变量应该在迭代期间保存?例子:
- 无限迭代器可能需要跟踪“第一个”项目,以便一旦到达序列末尾,它就可以从第一个项目恢复;
- 图迭代器可能需要在深度优先搜索中跟踪“最近的同级”,以便一旦到达分支的末尾,它就可以继续搜索下一个最近的同级。
有状态迭代器保存有关迭代的状态,以便可以找到下一项。在 Lua 中,如果迭代器函数是一个闭包(具有上值的函数)或函子(一个表现为函数的表,即具有一个__call
元方法)。向上值(闭包)或数据成员(函子)可以存储所需的状态。
无状态迭代器始终可以包装到有状态迭代器中。为了ipairs
:
function statefulIpairs(s)
local f, s, var = ipairs(s)
return function()
local i, v = f(s,var)
var = i
return i, v
end
end
这可以称为
tbl = {'a', 'b', 'c', 'd'}
sip = statefulIpairs(tbl) -- sip is stateful iter specific to tbl
for i,v in sip() do print(i,v) end
有状态迭代器的开发人员决定迭代器具有哪些功能:迭代器的 API 可能允许倒回、反转方向或其他操作。这在闭包的情况下甚至是可能的:可以使用附加参数来访问附加功能。例如,接受第三个参数,当该参数非 nil 时,将重置为序列的开头:
function resetableStatefulIpairs(s)
local f, s, var = ipairs(s)
local start = var
return function(a,b,reset)
if reset ~= nil then var = start; return end
local i, v = f(s,var)
var = i
return i, v
end
end
sip = resetableStatefulIpairs(tbl) -- sip is stateful iter specific to tbl
for i,v in sip() do print(i,v) end
sip(nil, nil, true) -- reset it
for i,v in sip() do print(i,v) end
Update一个更简洁的示例是如何生成一个接受命令的函数迭代器,以便您可以“...停止序列中的任何位置并迭代序列的其余部分 3 次”(按照 @deduplicator 的要求):
function iterGen(seq, start)
local cvar = start or 1
return function(cmd)
if cmd == nil then
if cvar > #seq then return nil, nil end
val = seq[cvar]
cvar = cvar + 1
return cvar-1, val
else
cmd = cmd[1]
if cmd == 'rewind' then
cvar = start or 1
elseif cmd == 'newstart' then
start = cvar
end
end
end
end
与上述:
> s = {1,2,3,4,5,6,7}
> iter = iterGen(s)
> for i,v in iter do print(i,v); if i==3 then break end end
1 1
2 2
3 3
> iter {'newstart'} -- save current as the new start pos
> for i,v in iter do print(i,v) end -- continue till end
4 4
5 5
6 6
7 7
> iter {'rewind'}
> for i,v in iter do print(i,v) end
4 4
5 5
6 6
7 7
> iter {'rewind'}
> for i,v in iter do print(i,v) end
4 4
5 5
6 6
7 7
正如所演示的,有状态迭代器没有什么特别之处,除了迭代状态位于迭代器内部这一事实之外,因此如上所述,开发人员需要公开所需的功能,例如上面的倒带和新开始。对于无状态迭代器,没有限制。
将迭代器设计为仿函数将是一个更自然的 API,因为这样迭代器“函数”就有了可以调用的“方法”,但创建一个可命令的函数是一个有趣的挑战。