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