Lua中有状态迭代器和无状态迭代器的区别

  • 本文关键字:状态 迭代器 区别 Lua lua
  • 更新时间 :
  • 英文 :


Lua中无状态迭代器和有状态迭代器有什么区别,请详细解释一下?什么时候我们需要使用无状态,什么时候使用另一个?我需要例子来理解这个概念。

首先让我们同意一个定义:(在Lua中)迭代器是一个函数类对象,每次调用它时返回序列中的下一个值。我认为重写for迭代是有帮助的,就像Lua ref manual:

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)"中计算出"下一个s项"。ipairs()会生成一个执行以下操作的迭代器:ipairs(s)返回三元组(iterFunction, s, 0);iterFunction可以给定s和索引0,然后返回1, s[1],然后返回2, s[2],等等(对于N项的表最终什么都没有)。

如果查找序列中的下一项需要多个循环控制变量怎么办?还是取决于其他变量的状态,这些变量应该在迭代期间保存?例子:

  • 无尽迭代器可能需要跟踪"第一"项,以便一旦到达序列的末尾,它可以从第一项恢复;
  • 图迭代器可能需要在深度优先搜索中跟踪"最近的兄弟节点",以便一旦到达分支的末端,它可以继续查找下一个最近的兄弟节点。

有状态迭代器保存关于迭代的状态,以便可以找到下一项。在Lua中,如果迭代器函数是闭包(具有上值的函数)或函子(具有函数行为的表,即具有__call元方法),则可以这样做。up值(闭包)或数据成员(函子)可以存储所需的状态。

无状态迭代器总是可以包装成有状态迭代器。对于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

如所示,除了迭代状态在迭代器内部这一事实外,有状态迭代器没有什么特别之处,因此,如上所述,开发人员可以公开所需的功能,如上面的rewind和newstart。对于无状态迭代器,没有限制。

将迭代器设计成函子是一个更自然的API,因为迭代器"函数"有可以调用的"方法",但是创建一个可命令的函数是一个有趣的挑战。

有状态迭代器和无状态迭代器的区别很简单:

有状态迭代器具有内部状态,因此您不能创建它们,运行它们一段时间,然后使用同一迭代器重复请求序列的结束。一个很好的例子是string.gmatch(...)

相比之下,无状态迭代器是其输入的纯函数,所有状态都是外部的。最著名的是pairs(a)(如果没有定义__pairs元方法,则返回a, next)和ipairs(如果没有定义' __ipairs元方法)。如果您想重复遍历它们序列的末尾,只需将参数隐藏在save.

相关内容

  • 没有找到相关文章

最新更新