Table.remove not working properly inside for / ipairs

I need to interact with an array and delete some elements.
But it seems table.remove shifts the Id and it’s messing the for / ipairs loop:

local arr = {}
for i = 1, 10, 1 do -- will create arr{"value1" ... "value10"}
	table.insert(arr, "value" .. i)
end

for i, v in ipairs(arr) do
	print(i, v)
	table.remove(arr, i)
end

This will print:

  1 value1
  2 value3
  3 value5
  4 value7
  5 value9

and the array will remain with:

    "value2",  "value4",  "value6", "value8", "value10"

What’s the correct way to interact with an array and remove items inside a loop?

1 Like

It’s generally not a good idea to use table.remove while iterating through an array’s elements, since the function shifts the positions of other elements to fill in the gap.

One way to work around it could be to record the elements to be removed, and remove afterwards in a second iteration.

local arr = {}
for i = 1, 10, 1 do -- will create arr{"value1" ... "value10"}
	table.insert(arr, "value" .. i)
end

local toBeRemoved = {}
for i, v in ipairs(arr) do
	print(i, v)
	table.insert(toBeRemoved, i)
end

-- start from the highest indexes so the shifting won't affect the placement of the next elements
table.sort(toBeRemoved)

for i = #toBeRemoved, 1, -1 do
	table.remove(arr, toBeRemoved[i])
end
6 Likes

Do this, table.remove will shift the elements index in the array

arr[i] = nil 

Note that if you do this it will no longer be an array, so to convert it back you could do this:

for i,v in pairs(arr) do
    arr[i] = nil
    table.insert(arr, v)
end
5 Likes

Go in reverse; for i = #array, 1, -1 do

3 Likes

I found a better way of doing it, you basically account for the number of removed entries so you can accurately find the correct index:

local arr = {}
for i = 1, 10, 1 do -- will create arr{"value1" ... "value10"}
	table.insert(arr, "value" .. i)
end

local removedEntries = 0

for i = 1, #arr do
	table.remove(arr, i - removedEntries)
	removedEntries = removedEntries + 1
end

--testing if the array is empty:
print(next(arr)) --will print nil
1 Like

Here’s an iterator factory that iteratores over the array in reverse order:

local function ipairs_r(t)
    local i = #t + 1
    return function()
        i = i - 1
        if t[i] then
            return i, t[i]
        end
    end
end

So you can do e.g.


local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for k, v in ipairs_r(t) do
    if v%2 == 0 then
        table.remove(t, k)
    end
end
print(table.concat(t, ", "))

to remove even numbers from the array.

2 Likes

In this case, i - removedEntries will be always 1.
But this led me to understand how table.remove works inside a loop: once it shifts the table, for each remove for an item with Id 1, the item with Id 2 becomes 1, Id 3 becomes 2 and so on…

Thus, the simplest solution would always be to remove Id 1 (not the for Id variable):

for i = 1, #arr do
	table.remove(arr, 1) -- Id 2 becomes 1, 3 becomes 2, ...
end
1 Like