Reputation: 3044
I don't understand why the following code produces error.
The code begins with the main()
function at the bottom.
heads = {}
function push(t)
if (#t == 2) then
table.insert(heads, t)
end
end
function remove(id)
for i = 1, #heads do
if (heads[i][2] == id) then
table.remove(heads, i)
end
end
end
function main()
push({50, 1})
push({50, 2})
push({50, 3})
remove(2)
end
When I run the code, I get attempt to index a nil value (field '?')
error.
I expect to push the subtable elements into the table and then remove only the second one. So the resulting elements can be {50, 1}
and {50, 3}
.
Why is my code not working and how to fix this?
Upvotes: 3
Views: 13413
Reputation: 12589
Andrew got it right. Never try to remove a value inside a table when you iterate the table. This is a common issue in many languages. Usually, you would store the value first and then remove like so:
local e
for i = 1, #heads do
if (heads[i][2] == id) then
e = i
break
end
end
if e then table.remove(heads, e) end
However, this solution is slow. Simply use the ID as key of your table:
local heads = {}
heads[1] = 50 -- push
heads[2] = 50
heads[3] = 50
heads[2] = nil -- remove
No need for unnecessary function calls and iterations.
Upvotes: 2
Reputation: 1
Addressing this iteration, in case of many items that are to be removed, I'd rather go for
local result = {}
for _, v in ipairs(myTab) do
if v == 'nice' then
table.insert(result, v)
end
end
myTab = result
as table.remove
is slow when shifting many elements.
Upvotes: 0
Reputation: 1403
As Andrew mentioned, for i = 1, #heads do
will go to the original length of the list; if you shorten heads
during the loop, then the final iteration(s) will read heads[i]
and find only nil
.
A simple way to fix this is to move backwards through the list, since removing an element only affects indices after the index you have removed from:
for i = #heads, 1, -1 do
if heads[i][2] == id then
table.remove(heads, i)
end
end
Note that in any case, this is O(n*d)
complexity and could be very slow if you are deleting many elements from the list. And, as others pointed out, there's a O(1)
approach where you use a map from v[1]
=> v
instead.
Upvotes: 1
Reputation: 1807
To avoid problems caused by removing fields while iterating over an array, I've used a while
loop with an index variable, which is incremented at the end of each iteration, but decremented when an index is removed. So, for instance, to remove all elements with an even index:
local t = { 1, 2, 3, 4, 5 }
local i = 1
while t[i] do
if t[i] % 2 == 0 then
table.remove(t, i)
i = i - 1
end
i = i + 1
end
This method allows you to iterate over array indices in ascending order.
Upvotes: 0
Reputation: 746
According to 5.1 manual table.remove "Removes from table the element at position, shifting down other elements to close the space, if necessary"
size of heads (#heads) is calculated once before loop execution, when i==2 you call table.remove, and so size of the table shrinks to 2, and on next iteration you try index heads[3][2], but heads[3] is nil, therefore "attempt to index a nil value" error message.
Upvotes: 3