Cette question est similaire à Comment puis-je parcourir en toute sécurité une table Lua lors du retrait des clés mais distinctement différent.
Étant donné un tableau Lua (table avec des clés qui sont des entiers séquentiels commençant par 1
), quel est le meilleur moyen de parcourir ce tableau et de supprimer certaines entrées telles qu'elles sont vues?
J'ai un tableau d'entrées horodatées dans une table de tableaux Lua. Les entrées sont toujours ajoutées à la fin du tableau (en utilisant table.insert
).
local timestampedEvents = {}
function addEvent( data )
table.insert( timestampedEvents, {getCurrentTime(),data} )
end
Je dois parfois parcourir cette table (dans l'ordre) et traiter et supprimer certaines entrées:
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
Malheureusement, l'approche du code ci-dessus rompt l'itération en sautant certaines entrées. Existe-t-il un meilleur moyen (moins de dactylographie, mais toujours sûr) de le faire que de parcourir manuellement les index:
function processEventsBefore( timestamp )
local i = 1
while i <= #timestampedEvents do -- warning: do not cache the table length
local stamp = timestampedEvents[i]
if stamp[1] <= timestamp then
processEventData( stamp[2] )
table.remove( timestampedEvents, i )
else
i = i + 1
end
end
end
J'éviterais table.remove
et traverserais le tableau une fois les entrées non désirées définies sur nil
, puis traverserais à nouveau le tableau en le compactant si nécessaire.
Voici le code que j'ai en tête, en utilisant l'exemple de la réponse de 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
le cas général d'itération sur un tableau et de suppression d'éléments aléatoires du milieu tout en continuant l'itération
Si vous effectuez une itération de bout en bout, lorsque vous supprimez l'élément N, l'élément suivant de votre itération (N + 1) est déplacé dans cette position. Si vous incrémentez votre variable d'itération (comme le fait ipairs), vous ignorez cet élément. Il y a deux façons de gérer cela.
En utilisant cet exemple de données:
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 }
Nous pouvons supprimer des éléments input
pendant l'itération en:
Itérer de l'arrière à l'avant.
for i=#input,1,-1 do
if remove[input[i]] then
table.remove(input, i)
end
end
Contrôler manuellement la variable de boucle afin de ne pas l'incrémenter lors de la suppression d'un élément:
local i=1
while i <= #input do
if remove[input[i]] then
table.remove(input, i)
else
i = i + 1
end
end
Pour les tables autres que des tableaux, vous effectuez une itération à l'aide de next
ou pairs
(implémenté sous la forme de next
) et définissez les éléments que vous souhaitez supprimer sur nil
.
Notez que table.remove
décale tous les éléments suivants à chaque appel, les performances sont donc exponentielles pour N suppressions. Si vous supprimez de nombreux éléments, vous devez les déplacer vous-même, comme dans LHF ou la réponse de Mitch.
Essayez cette fonction:
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
Il n'utilise pas table.remove
, il devrait donc avoir la complexité O(N)
. Vous pouvez déplacer la fonction remove
dans le générateur for pour supprimer le besoin d'une valeur supérieure, mais cela signifierait une nouvelle fermeture pour chaque élément ... et ce n'est pas une question pratique.
Exemple d'utilisation:
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, ','))
Veillez à ne pas utiliser break
(ni à sortir prématurément de la boucle) car cela laisserait le tableau avec des éléments nil
.
Si vous voulez que break
signifie "abort" (comme dans, rien n'est supprimé), vous pouvez faire ceci:
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
Cela présente l'avantage de pouvoir annuler la totalité de la boucle sans qu'aucun élément ne soit supprimé, tout en offrant la possibilité de sauter des éléments déjà marqués comme "à supprimer". L'inconvénient est le surcoût d'une nouvelle table.
J'espère que cela vous sera utile.
Je déconseille d'utiliser table.remove
, pour des raisons de performances (qui peuvent être plus ou moins pertinentes pour votre cas particulier).
Voici à quoi ressemble ce type de boucle pour moi:
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
Note Ceci est rapide MAIS avec deux mises en garde:
Si vous souhaitez conserver l'ordre des éléments ou si vous prévoyez ne pas conserver la plupart des éléments, examinez la solution de Mitch. Voici une comparaison approximative entre le mien et le sien. Je l'ai exécuté sur https://www.lua.org/cgi-bin/demo et la plupart des résultats étaient similaires à ceux-ci:
[ srekel] elapsed time: 0.020
[ mitch] elapsed time: 0.040
[ srekel] elapsed time: 0.020
[ mitch] elapsed time: 0.040
Bien sûr, rappelez-vous que cela varie en fonction de vos données.
Voici le code pour le test:
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")
Vous pouvez envisager d'utiliser une file d'attente priority au lieu d'un tableau trié . Une file d'attente prioritaire se compacte efficacement lorsque vous supprimez des entrées dans l'ordre.
Pour un exemple d'implémentation d'une file d'attente de priorités, voir le fil de discussion suivant: http://lua-users.org/lists/lua-l/2007-07/msg00482.html
Il me semble que, dans mon cas particulier, où je ne déplace jamais que les entrées du début de la file d'attente, je peux le faire beaucoup plus simplement via:
function processEventsBefore( timestamp )
while timestampedEvents[1] and timestampedEvents[1][1] <= timestamp do
processEventData( timestampedEvents[1][2] )
table.remove( timestampedEvents, 1 )
end
end
Cependant, je n'accepterai pas cela comme solution car il ne gère pas le cas général d'itération sur un tableau et de suppression d'éléments aléatoires du milieu tout en poursuivant l'itération.
Vous pouvez utiliser un foncteur pour vérifier les éléments à supprimer. Le gain supplémentaire est qu'il se termine dans O (n), car il n'utilise pas 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
Usage:
table.iremove_if(myList, function(i,v) return v.name == name end)
Dans ton cas:
table.iremove_if(timestampedEvents, function(_,stamp)
if (stamp[1] <= timestamp) then
processEventData(stamp[2])
return true
end
end)
Simple..
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