以下 Lua 迭代器是否无状态?

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

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 本地函数中更改该表中的字段。那么这个迭代器是无状态的还是具有复杂的状态?如果它不是无状态的,有没有一种使用无状态迭代器实现此功能的方法?

谢谢。

点赞
用户5675002
用户5675002

PIL 书中将其称为“具有复杂状态的迭代器”。

http://www.lua.org/pil/7.4.html

2016-08-03 08:08:16
用户2505965
用户2505965

这不是一个stateless iterator,而是一个有complex state的迭代器。

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

您可以考虑使用闭包来实现这个。虽然不是完全无状态,但使用了upvalues而不是表查找,应该更有效率。

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
2016-08-03 08:46:44
用户3677376
用户3677376

PIL关于无状态迭代器的章节说明:

正如其名称所示,无状态迭代器是一种不保留任何状态的迭代器。因此,我们可以在多个循环中使用同一个无状态迭代器,避免创建新闭包的成本。

在代码中,这意味着以下两个 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 仍然使用此方法,因为它无法重新创建迭代器元组(它通过函数参数从外部代码传递),并且对于某些算法,您绝对需要多次运行相同的迭代。

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

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

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

2016-08-03 08:55:47