Lua中的无状态迭代器和有状态迭代器有什么区别,请详细解释一下?我们什么时候需要使用无状态和另一个?我需要一些例子来理解这个概念。
答案 0 :(得分:8)
首先让我们就定义达成一致:(在Lua中)迭代器是一个 function 类似的对象,每次调用它时都会返回序列中的下一个值。我认为重写for
迭代有助于完成Lua ref手册:
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
其中expression
是数据的三元组(或返回这种三元组的函数调用):
func
是实际的迭代器函数seq
是正在迭代的序列controlVar
是循环控制变量迭代状态是找到迭代序列中的下一个项目所需的任何状态。因此,无状态迭代器是func
不包含任何此类状态的迭代器:您可以随时调用func(seq, controlVar)
,返回值将始终相同(如果seq未更改);它不取决于通话前发生的事情。
如上所示,Lua支持一个循环控制变量。因此,为了使序列可以通过无状态迭代器进行迭代,必须能够根据一个循环控制变量确定序列中的下一个项目。即,必须能够单独从“(s,controlVar)”中找出“下一个项目”。 ipairs()
生成一个执行此操作的迭代器:ipairs(s)
返回三元组(iterFunction, s, 0)
; iterFunction
可以被赋予s
并且索引为0,然后返回1, s[1]
,然后返回2, s[2]
等等(对于N个项目的表,最终没有。)
如果查找序列中的下一个项目需要多个循环控制变量,该怎么办?或者取决于其他变量的状态,这些变量应该在迭代期间保存?示例:
有状态迭代器保存有关迭代的状态,以便可以找到下一个项目。在Lua中,如果迭代器函数是一个闭包(具有upvalues的函数)或一个functor(一个表现为函数的表,即具有__call
元方法),则这是可能的。 up值(闭包)或数据成员(functor)可以存储所需的状态。
无状态迭代器总是可以包装到有状态迭代器中。对于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可以允许倒带,反转方向或其他操作。在闭包的情况下甚至可以实现:可以使用附加参数来访问其他功能。例如,接受第三个参数,当非零时,重置为序列的开头:
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
更新一个更简洁的例子是如何生成一个接受命令的函数迭代器,这样你就可以“......停止序列中的任何地方并迭代序列的其余部分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
如上所述,除了迭代状态在迭代器内部之外,有状态迭代器没有什么特别之处,因此如上所述,开发人员需要公开所需的功能,如上面的rewind和newstart。使用无状态迭代器,没有限制。
将迭代器设计为仿函数将是一个更自然的API,因为迭代器“函数”具有可以调用的“方法”,但创建可命令函数是一个有趣的挑战。
答案 1 :(得分:2)
有状态和无状态迭代器之间的区别很简单:
有状态迭代器具有内部状态,因此您无法创建它们,稍微运行它们,然后使用相同的迭代器重复请求序列结束。 string.gmatch(...)
返回了一个很好的例子。
相比之下,无状态迭代器是其输入的纯函数,所有状态都是外部的。最着名的是pairs(a)
返回(如果没有定义a, next
元方法则返回__pairs
)和ipairs
返回(如果没有`__ipairs元方法则返回{{1}}。如果你想反复遍历序列的结尾,只需将参数存储在某处保存。