【问题标题】:Is the following Lua iterator stateless?下面的 Lua 迭代器是无状态的吗?
【发布时间】:2016-08-03 17:35:49
【问题描述】:

我对无状态迭代器的概念感到困惑。作为练习,我编写了一个迭代器来打印给定字符串的所有非空子字符串。代码如下。

local function iter(state, i)
    local str = state.str
    if type(str) ~= "string" or str == "" then return nil end
    if state.ending > #str then return nil end

    local start = state.start
    local ending = state.ending

    if start == ending then
        state.ending = ending + 1
        state.start = 1
    else
        state.start = start + 1
    end

    return string.sub(str, start, ending)
end

function allSubstrings(str)
    return iter, { str = str, start = 1, ending = 1 }, nil
end

for substr in allSubstrings("abcd123") do
    print(substr)
end

我使用一个表{ str = str, start = 1, ending = 1 }作为所谓的不变状态,但是我必须在iter本地函数中更改这个表中的字段。那么这个迭代器是无状态的还是具有复杂状态的呢?如果它不是无状态的,有没有办法用无状态迭代器来实现这个功能?

谢谢。

【问题讨论】:

    标签: lua iterator


    【解决方案1】:

    PIL-chapter about stateless iterators 声明:

    顾名思义,无状态迭代器是一个自身不保持任何状态的迭代器。因此,我们可以在多个循环中使用相同的无状态迭代器,从而避免创建新闭包的成本。

    在代码中,这意味着 for 都在循环:

    f, s, var = pairs( t )
    for k,v in f, s, var do print( k, v ) end
    for k,v in f, s, var do print( k, v ) end
    

    应该产生相同的输出。这仅在不变状态 s 和迭代器函数 f 的任何上值都没有在迭代期间发生变化时才有效,否则第二个 for 循环将具有与第一个循环不同的开始条件。

    所以,不,您的迭代器不是无状态迭代器,因为您在迭代期间更改了不变状态。

    有一种方法可以使您的迭代器无状态(流行的 Lua 库 luafun 广泛使用这种技术):将所有可变状态保留在循环控制变量 var 中(即使您需要在每个分配步骤):

    local function iter( str, var )
      if type( str ) ~= "string" or str == "" then return nil end
      if var[ 2 ] > #str then return nil end
      local start, ending = var[ 1 ], var[ 2 ]
      if start == ending then
        return { 1, ending+1 }, string.sub( str, start, ending )
      else
        return { start+1, ending }, string.sub( str, start, ending )
      end
    end
    
    function allSubstrings2( str )
      return iter, str, { 1, 1 }
    end
    
    for _,substr in allSubstrings2( "abcd123" ) do
      print( substr )
    end
    

    缺点是第一个迭代变量var(循环控制变量)对迭代器的用户没有任何用处,并且由于您必须为每个迭代步骤分配一个表,“避免了创建新的成本闭包”对于另一个循环并不重要。

    luafun 无论如何都会这样做,因为它无法重新创建迭代器元组(它通过外部代码的函数参数传递),并且对于某些算法,您绝对需要多次运行相同的迭代。

    这种方法的替代方法是将所有可变状态集中在“不变状态”sf 的所有上值必须是不可变的),并提供一种复制/克隆此状态以供以后迭代的方法:

    f, s, var = allSubstrings("abcd123")
    s2 = clonestate( s )
    for str in f, s, var do print( str ) end
    for str in f, s2, var do print( str ) end
    

    这仍然不是无状态迭代器,但它比具有堆分配循环控制变量的无状态迭代器更节省内存,并且它允许您重新开始迭代。

    【讨论】:

    • 非常感谢。我想现在对我来说更清楚了。我只是在阅读 PIL,但我无法理解这一点。
    • 如果你在无状态的方法中不返回一个新创建的表,而是直接修改var并返回它,会有什么不同吗?
    • @user1187195 这将阻止您重新开始迭代,因为第二个循环中的var 将包含与第一个循环中不同的值。虽然在 PIL 中关于无状态迭代器的章节中没有明确提到这种情况,但我想说这会使迭代器失去无状态的资格......
    【解决方案2】:

    这不是stateless iterator,它确实是带有complex state 的迭代器。

    在无状态迭代器中只有一个控制变量,在整个迭代器中它应该被视为一个纯值。

    您可以考虑使用闭包来实现这一点。不是完全无状态的,但确实在查找表时使用了上值,这应该更有效。

    local function allSubstrings (str)
        if type(str) ~= "string" or str == "" then
            return function () end -- NOP out on first iteration
        end
    
        local length = #str
        local start, ending = 1, 1
    
        return function ()
            if ending > length then return nil end
    
            local st, ed = start, ending
    
            if start == ending then
                ending = ending + 1
                start = 1
            else
                start = start + 1
            end
    
            return string.sub(str, st, ed)
        end
    end
    
    for substr in allSubstrings("abcd123") do
        print(substr)
    end
    

    【讨论】:

      【解决方案3】:

      PIL 书中称其为“具有复杂状态的迭代器”
      http://www.lua.org/pil/7.4.html

      【讨论】:

        猜你喜欢
        • 2014-05-08
        • 1970-01-01
        • 2018-02-16
        • 2022-01-15
        • 2017-04-04
        • 2018-12-03
        • 2011-08-12
        • 2014-09-26
        • 2017-04-01
        相关资源
        最近更新 更多