Lua 在迭代时安全地从数组表中删除项

Lua 在迭代时安全地从数组表中删除项,lua,Lua,这个问题类似,但明显不同 总结 给定一个Lua数组(表中的键是从1开始的顺序整数),迭代该数组并删除某些项的最佳方法是什么 现实世界的例子 我在Lua数组表中有一个时间戳条目数组。条目总是添加到数组的末尾(使用table.insert) 我需要偶尔(按顺序)浏览此表,并处理和删除某些条目: function processEventsBefore( timestamp ) for i,stamp in ipairs( timestampedEvents ) do if stamp[1

这个问题类似,但明显不同

总结 给定一个Lua数组(表中的键是从
1
开始的顺序整数),迭代该数组并删除某些项的最佳方法是什么

现实世界的例子 我在Lua数组表中有一个时间戳条目数组。条目总是添加到数组的末尾(使用
table.insert

我需要偶尔(按顺序)浏览此表,并处理和删除某些条目:

function processEventsBefore( timestamp )
  for i,stamp in ipairs( timestampedEvents ) do
    if stamp[1] <= timestamp then
      processEventData( stamp[2] )
      table.remove( timestampedEvents, i )
    end
  end
end
函数处理事件之前(时间戳)
对于i,在ipair(timestampedEvents)中盖章

如果stamp[1]我会想到,对于我的特殊情况,我只会从队列前面移动条目,我可以通过以下方式更简单地执行此操作:

function processEventsBefore( timestamp )
  while timestampedEvents[1] and timestampedEvents[1][1] <= timestamp do
    processEventData( timestampedEvents[1][2] )
    table.remove( timestampedEvents, 1 )
  end
end
函数处理事件之前(时间戳)
而timestampedEvents[1]和timestampedEvents[1][1]
在数组上迭代并从中间删除随机项,同时继续迭代的一般情况

如果您是前后迭代,当您删除元素N时,迭代中的下一个元素(N+1)会向下移动到该位置。如果您增加迭代变量(就像ipairs那样),您将跳过该元素。有两种方法可以解决这个问题

使用此示例数据:

    input = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p' }
    remove = { f=true, g=true, j=true, n=true, o=true, p=true }
我们可以通过以下方式在迭代过程中删除
输入
元素:

  • 从后向前迭代

    for i=#input,1,-1 do
        if remove[input[i]] then
            table.remove(input, i)
        end
    end
    
  • 手动控制循环变量,这样我们可以在删除元素时跳过递增:

    local i=1
    while i <= #input do
        if remove[input[i]] then
            table.remove(input, i)
        else
            i = i + 1
        end
    end
    
    locali=1
    
    当我时,我会避开
    表。删除
    并遍历数组,一旦将不需要的条目设置为
    nil
    ,然后再次遍历数组,如有必要,将其压缩

    下面是我想到的代码,使用Mud答案中的示例:

    local input = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p' }
    local remove = { f=true, g=true, j=true, n=true, o=true, p=true }
    
    local n=#input
    
    for i=1,n do
            if remove[input[i]] then
                    input[i]=nil
            end
    end
    
    local j=0
    for i=1,n do
            if input[i]~=nil then
                    j=j+1
                    input[j]=input[i]
            end
    end
    for i=j+1,n do
            input[i]=nil
    end
    

    可以考虑使用A而不是排序数组。 当您按顺序删除条目时,优先级队列将有效地压缩自身

    有关优先级队列实现的示例,请参阅此邮件列表线程:

    尝试此功能:

    function ripairs(t)
        -- Try not to use break when using this function;
        -- it may cause the array to be left with empty slots
        local ci = 0
        local remove = function()
            t[ci] = nil
        end
        return function(t, i)
            --print("I", table.concat(array, ','))
            i = i+1
            ci = i
            local v = t[i]
            if v == nil then
                local rj = 0
                for ri = 1, i-1 do
                    if t[ri] ~= nil then
                        rj = rj+1
                        t[rj] = t[ri]
                        --print("R", table.concat(array, ','))
                    end
                end
                for ri = rj+1, i do
                    t[ri] = nil
                end
                return
            end
            return i, v, remove
        end, t, ci
    end
    
    它不使用
    表。删除
    ,因此它应该具有
    O(N)
    复杂性。您可以将
    remove
    函数移动到for生成器中,以消除对upvalue的需要,但这将意味着每个元素都有一个新的闭包。。。这不是一个实际问题

    用法示例:

    function math.isprime(n)
        for i = 2, n^(1/2) do
            if (n % i) == 0 then
                return false
            end
        end
        return true
    end
    
    array = {}
    for i = 1, 500 do array[i] = i+10 end
    print("S", table.concat(array, ','))
    for i, v, remove in ripairs(array) do
        if not math.isprime(v) then
            remove()
        end
    end
    print("E", table.concat(array, ','))
    
    小心不要使用
    break
    (或过早退出循环),因为它将使用
    nil
    元素离开数组

    如果希望
    break
    表示“中止”(如中所示,不删除任何内容),可以执行以下操作:

    function rtipairs(t, skip_marked)
        local ci = 0
        local tbr = {} -- "to be removed"
        local remove = function(i)
            tbr[i or ci] = true
        end
        return function(t, i)
            --print("I", table.concat(array, ','))
            local v
            repeat
                i = i+1
                v = t[i]
            until not v or not (skip_marked and tbr[i])
            ci = i
            if v == nil then
                local rj = 0
                for ri = 1, i-1 do
                    if not tbr[ri] then
                        rj = rj+1
                        t[rj] = t[ri]
                        --print("R", table.concat(array, ','))
                    end
                end
                for ri = rj+1, i do
                    t[ri] = nil
                end
                return
            end
            return i, v, remove
        end, t, ci
    end
    
    这样做的优点是可以取消整个循环,而不删除任何元素,还可以提供跳过已标记为“待删除”的元素的选项。缺点是新表的开销


    我希望这些对您有帮助。

    我建议不要使用
    表。出于性能原因(可能或多或少与您的特定情况相关),请删除

    下面是这种类型的循环对我来说通常是什么样子:

    local mylist_size = #mylist
    local i = 1
    while i <= mylist_size do
        local value = mylist[i]
        if value == 123 then
            mylist[i] = mylist[mylist_size]
            mylist[mylist_size] = nil
            mylist_size = mylist_size - 1
        else
            i = i + 1
        end
    end
    
    当然,请记住,这取决于您的特定数据

    以下是测试代码:

    function test_srekel(mylist)
        local mylist_size = #mylist
        local i = 1
        while i <= mylist_size do
            local value = mylist[i]
            if value == 13 then
                mylist[i] = mylist[mylist_size]
                mylist[mylist_size] = nil
                mylist_size = mylist_size - 1
            else
                i = i + 1
            end
        end
    
    end -- func
    
    function test_mitch(mylist)
        local j, n = 1, #mylist;
    
        for i=1,n do
            local value = mylist[i]
            if value ~= 13 then
                -- Move i's kept value to j's position, if it's not already there.
                if (i ~= j) then
                    mylist[j] = mylist[i];
                    mylist[i] = nil;
                end
                j = j + 1; -- Increment position of where we'll place the next kept value.
            else
                mylist[i] = nil;
            end
        end
    end
    
    function build_tables()
        local tables = {}
        for i=1, 10 do
          tables[i] = {}
          for j=1, 100000 do
            tables[i][j] = j % 15373
          end
        end
    
        return tables
    end
    
    function time_func(func, name)
        local tables = build_tables()
        time0 = os.clock()
        for i=1, #tables do
            func(tables[i])
        end
        time1 = os.clock()
        print(string.format("[%10s] elapsed time: %.3f\n", name, time1 - time0))
    end
    
    time_func(test_srekel, "srekel")
    time_func(test_mitch, "mitch")
    time_func(test_srekel, "srekel")
    time_func(test_mitch, "mitch")
    
    功能测试\u srekel(mylist)
    本地mylist_大小=#mylist
    局部i=1
    
    当i时,可以使用函子检查需要删除的元素。额外的好处是它在O(n)中完成,因为它不使用table.remove

    function table.iremove_if(t, f)
        local j = 0
        local i = 0
        while (i <= #f) do
            if (f(i, t[i])) then
                j = j + 1
            else
                i = i + 1
            end
            if (j > 0) then
                local ij = i + j
                if (ij > #f) then
                    t[i] = nil
                else
                    t[i] = t[ij]
                end
            end
        end
        return j > 0 and j or nil -- The number of deleted items, nil if 0
    end
    
    就你而言:

    table.iremove_if(timestampedEvents, function(_,stamp)
        if (stamp[1] <= timestamp) then
            processEventData(stamp[2])
            return true
        end
    end)
    
    table.iremove\u if(timestampedEvents,function(\uu,stamp)
    如果(盖章[1]效率!
    警告:不要使用table.remove()。该函数会导致每次调用它以删除数组项时,所有后续(以下)数组索引都会被重新索引。因此,只需在一次传递中“压缩/重新索引”该表要快得多!

    最好的方法很简单:通过所有数组项向上计数(
    i
    ),同时跟踪我们应该将下一个“保留”值放入(
    j
    )的位置。任何没有保留的(或从
    i
    移动到
    j
    )都设置为
    nil
    ,这告诉Lua我们已经删除了该值

    我分享这一点,因为我真的不喜欢这一页上的其他答案(截至2018年10月)。它们要么是错误的、充满bug的、过于简单化的或过于复杂的,而且大多数都是超慢的。因此我实现了一个高效、干净、超快的单循环算法

    下面是一个完整的注释示例(本文末尾有一个较短的非教程版本):

    函数数组显示(t)
    当i=1时,则不行
    打印('total:'..t,'i:'..i,'v:'..t[i]);
    结束
    结束
    函数ArrayRemove(t,fnKeep)
    打印('之前:');
    ArrayShow(t);
    印刷品(“--”);
    局部j,n=1,#t;
    对于i=1,n do
    打印('i:'..i,'j:'..j);
    如果(fn保持(t,i,j)),那么
    如果(i~=j)那么
    打印('保留:'..i',移动到:'..j);
    --保持i的值,移动到j的位置。
    t[j]=t[i];
    t[i]=nil;
    其他的
    --保持i的值,已经在j的位置。
    打印('保留:'..i',已在:'..j);
    结束
    j=j+1;
    其他的
    t[i]=nil;
    结束
    结束
    印刷品(“--”);
    打印('后:');
    ArrayShow(t);
    返回t;
    结束
    局部t={
    “a”、“b”、“c”、“d”、“e”、“f”、“g”、“h”、“i”
    };
    ArrayRemove(t,函数(t,i,j)
    --返回true保留该值,或返回false放弃该值。
    局部v=t[i];
    返回(v=='a'或v=='b'或v=='f'或v=='h');
    (完),;
    
    输出,一路上显示它的逻辑,它是如何移动事物的,等等

    before:
    total:9 i:1 v:a
    total:9 i:2 v:b
    total:9 i:3 v:c
    total:9 i:4 v:d
    total:9 i:5 v:e
    total:9 i:6 v:f
    total:9 i:7 v:g
    total:9 i:8 v:h
    total:9 i:9 v:i
    ---
    i:1 j:1
    keeping:1   already at:1
    i:2 j:2
    keeping:2   already at:2
    i:3 j:3
    i:4 j:3
    i:5 j:3
    i:6 j:3
    keeping:6   moving to:3
    i:7 j:4
    i:8 j:4
    keeping:8   moving to:4
    i:9 j:5
    ---
    after:
    total:4 i:1 v:a
    total:4 i:2 v:b
    total:4 i:3 v:f
    total:4 i:4 v:h
    
    最后,这是一个可以在你自己的代码中使用的函数,不需要所有的教程打印,只需要一个fe
    function table.iremove_if(t, f)
        local j = 0
        local i = 0
        while (i <= #f) do
            if (f(i, t[i])) then
                j = j + 1
            else
                i = i + 1
            end
            if (j > 0) then
                local ij = i + j
                if (ij > #f) then
                    t[i] = nil
                else
                    t[i] = t[ij]
                end
            end
        end
        return j > 0 and j or nil -- The number of deleted items, nil if 0
    end
    
    table.iremove_if(myList, function(i,v) return v.name == name end)
    
    table.iremove_if(timestampedEvents, function(_,stamp)
        if (stamp[1] <= timestamp) then
            processEventData(stamp[2])
            return true
        end
    end)
    
    before:
    total:9 i:1 v:a
    total:9 i:2 v:b
    total:9 i:3 v:c
    total:9 i:4 v:d
    total:9 i:5 v:e
    total:9 i:6 v:f
    total:9 i:7 v:g
    total:9 i:8 v:h
    total:9 i:9 v:i
    ---
    i:1 j:1
    keeping:1   already at:1
    i:2 j:2
    keeping:2   already at:2
    i:3 j:3
    i:4 j:3
    i:5 j:3
    i:6 j:3
    keeping:6   moving to:3
    i:7 j:4
    i:8 j:4
    keeping:8   moving to:4
    i:9 j:5
    ---
    after:
    total:4 i:1 v:a
    total:4 i:2 v:b
    total:4 i:3 v:f
    total:4 i:4 v:h
    
    [     mitch] elapsed time: 2.802
    
    ^Clua: test.lua:4: interrupted!
    stack traceback:
        [C]: in function 'table.remove'
        test.lua:4: in function 'test_tableremove'
        test.lua:43: in function 'time_func'
        test.lua:50: in main chunk
        [C]: in ?
    
    values = {'a', 'b', 'c', 'd', 'e', 'f'}
    rem_key = {}
    
    for i,v in pairs(values) do
    if remove_value() then
    table.insert(rem_key, i)
    end
    end
    
    for i,v in pairs(rem_key) do
    table.remove(values, v)
    end
    
    --helper function needed for remove(...)
    --I’m not super able to explain it, check the link above
    function isarray(tableT)
        for k, v in pairs(tableT) do
            if tonumber(k) ~= nil and k ~= #tableT then
                if tableT[k+1] ~= k+1 then
                    return false
                end
            end
        end
        return #tableT > 0 and next(tableT, #tableT) == nil
     end
    
    function remove(targetTable, removeMe)
    --check if this is an array
    if isarray(targetTable) then
        --flag for when a table needs to squish in to fill cleared space
        local shouldMoveDown = false
        --iterate over table in order
        for i = 1, #targetTable do
            --check if the value is found
            if targetTable[i] == removeMe then
                --if so, set flag to start collapsing the table to write over it
                shouldMoveDown = true
            end
            --if collapsing needs to happen...
            if shouldMoveDown then
                --check if we're not at the end
                if i ~= #targetTable then
                    --if not, copy the next value over this one
                    targetTable[i] = targetTable[i+1]
                else
                    --if so, delete the last value
                    targetTable[#targetTable] = nil
                end 
            end
        end
    else
        --loop over elements
        for k, v in pairs(targetTable) do
            --check for thing to remove
            if (v == removeMe) then
                --if found, nil it
                targetTable[k] = nil
                break
            end
        end
    end
    return targetTable, removeMe;
    
    function ArrayRemove(t, fnKeep)
        local j, n = 1, #t;
        for i=1,n do
            if (fnKeep(t, i, j)) then
                -- Move i's kept value to j's position, if it's not already there.
                if (i ~= j) then
                    t[j] = t[i];
                end
                j = j + 1; -- Increment position of where we'll place the next kept value.
            end
        end
        table.move(t,n+1,n+n-j+1,j);
        --for i=j,n do t[i]=nil end
        return t;
    end
    
    function ArrayRemove(t, fnKeep)
        local i, j, n = 1, 1, #t;
        while i <= n do
            if (fnKeep(t, i, j)) then
                local k = i
                repeat
                    i = i + 1;
                until i>n or not fnKeep(t, i, j+i-k)
                --if (k ~= j) then
                    table.move(t,k,i-1,j);
                --end
                j = j + i - k;
            end
            i = i + 1;
        end
        table.move(t,n+1,n+n-j+1,j);
        return t;
    end