【问题标题】:Difference between stateful and stateless iterators in LuaLua 中有状态迭代器和无状态迭代器的区别
【发布时间】:2014-05-08 13:54:37
【问题描述】:

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

【问题讨论】:

    标签: lua


    【解决方案1】:

    首先让我们就定义达成一致:(在 Lua 中)迭代器是一个类似 函数 的对象,每次调用它都会返回序列中的下一个值。我认为重写 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 支持 one 循环控制变量。因此,为了使序列可以通过无状态迭代器进行迭代,必须能够根据 one 循环控制变量确定序列中的下一项。即,必须可以单独从“(s,controlVar)”中找出“下一个项目”。 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
    

    更新 一个更简洁的例子是如何生成一个函数迭代器,它接受命令,这样您就可以“...停止序列中的任何位置并迭代序列的其余部分 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,因为从那时起迭代器“函数”具有可以调用的“方法”,但创建可命令函数是一个有趣的挑战。

    【讨论】:

    • 非常感谢,非常感谢您的回答,否则向像我这样的菜鸟解释它真的很难:) 谢谢 :)
    • @Simrankaur 很高兴这对你有意义。由于您有足够的积分进行投票,因此您应该尝试对答案进行投票或评论不清楚的内容,以防作者可以修复-这可以提高 SO 质量。干杯!
    【解决方案2】:

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

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

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

    【讨论】:

    • 有趣,但我不明白这一点 :) 有状态迭代器 不能 被重复请求以结束序列?如果gmatch 是无状态的而不是有状态的,你能举个例子吗?另外,你怎么知道gmatch 是有状态的?如果您可以扩展您的答案(而不是评论),那将不胜感激。
    • 查看lua docs(5.2版)中关于string.gmatch的第一句。
    • AFAICT(来自测试),您可以存储从gmatch 返回的函数并在将来随时调用它,即使没有对字符串的引用(因此函数中有一个 ref )。
    • 是的,你可以。你想说什么?您无法重复获取它,因为您保存的唯一一个对象具有所有内部状态,它是有状态的。
    • 你的意思是你不能用返回的迭代器函数重复遍历整个序列。这不是有状态迭代器的限制,只是gmatch 提供的迭代器的实现(见我的回答)。
    猜你喜欢
    • 1970-01-01
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多