To really understand this, it’s helpful to know why editing while iterating over an array can sometimes be an issue.
It arises when the “editing” actually means moving and/or removing elements from the array. From https://www.lua.org/pil/19.2.html:
The table.remove
function removes (and returns) an element from a given position in an array, moving down other elements to close space and decrementing the size of the array. When called without a position, it removes the last element of the array.
In other words, calling table.remove(t, i)
changes the index of every element after the i
’th.
When happens when we remove e.g. the 1st element is that the second element is moved down into the 1st position, the 2nd to the 3rd, and so on. When the next iteration in the loop tries to do something to the “next” element, that’s the element in the 2nd position which is the 3rd element. I.e. it skips the 2nd element!
Here’s an example:
local t = {'a', 'b', 'c'}
for k, v in ipairs(t) do
if v == 'a' or v == 'b' then
table.remove(t, k)
end
end
print(table.concat(t, ", "))
You’d think that it would remove any elements that are ‘a’ or ‘b’, but it doesn’t remove ‘b’ because that gets skipped. You can work around it by manually keeping track of how many elements were removed, like so:
local t = {'a', 'b', 'c'}
local offset = 0
for k = 1, #t do
local k = k - offset
local v = t[k]
if v == 'a' or v == 'b' then
table.remove(t, k)
offset = offset + 1
end
end
print(table.concat(t, ", "))
… or more elegantly by iterating in reverse order:
local t = {'a', 'b', 'c'}
for k = #t, 1, -1 do
local v = t[k]
if v == 'a' or v == 'b' then
table.remove(t, k)
end
end
print(table.concat(t, ", "))
The reason then that “editing while iterating” over a dict is that removing elements from a dict doesn’t shift any other elements around. You might still get issues if you do some other kind of “editing” that changes the indices of keys that haven’t been iterated over yet.