Hi!
So recently, while developing my game, I've found I've used tables more than I have ever before writing standalone scripts and such, as they're generally useful for saving space, keeping a solid index, etc.
However, I've ran into several occasions where I would have to use table.remove
, on say, a player that's in a table, but I don't know the index that the player's in (i.e. 1st value, 2nd value, etc.)
I know it's possible using for loops, cycling through the table until you find the player, and get the player's position in the table that way, but I feel like that's terribly inefficient.
So, my question is-
What's the easiest way to remove a value from a table without knowing what position in the table it has?
Thanks for reading; all replies are appreciated!
Lua doesn't have a "remove this value" function, probably because there's a few ambiguities in what that would mean:
nil
or by removing from a list? e.g. {1,2,3}
becoming {1,3}
or {1,nil,3}
The simplest solution is to remove one value and to pick the appropriate one for the second bullet.
The short answer then, is to remove an element whose index we don't know, we just figure out what the index is. We need to know this index.
There are of course faster ways to search (however this usually will not be prohibitive -- especially not on small data like a list of players playing or even total cumulative players using DataStores).
This is O(n)
, where n
is the size of the table. This is usually fast enough, except when it is repeatedly done (but even for n
like 1000 or 2000 this is usually acceptably fast since it is so straightforward to implement and understand)
Lua, for unknown reasons to me, does not have a "final element" function, but it's easy to write one:
function tablefind(tab,el) for index, value in pairs(tab) do if value == el then return index end end end
To delete val
from tab
, we can then use
table.remove( tab, tablefind(tab, val) )
This will only work when your tables are purely lists (using only indices [1,2,3,..]) because table.remove
will not work on other numbers. (You would instead use tab[ind] = nil
)
For sortable data, like numbers or strings, if you are searching in a sorted list, then you can get away with just O(log(n))
comparisons (e.g., on 2000 elements, just 11 comparisons).
These comparisons are more expensive than in a linear search, though -- and it also requires the data be sorted, and O(n * log(n))
operation -- which is slower than a single (or a small fixed number) of linear searches.
Binary search works by looking if it's higher or lower than a given guess. We can implement it like this:
function binary(tab, val, cmp, low, high) -- cmp is a function so that -- cmp(a,b) indicates a < b. if not low then return binary(tab, val, cmp, 1, #tab) end local middle = math.floor(low/2 + high/2) local mid = tab[middle] if mid == val then return middle end if low >= high then return nil end if cmp(val,middle) then return binary(tab,val,cmp, 1, middle - 1) end return binary(tab,val, cmp, middle + 1, high) end
Lua's tables are implemented as hashtables. That means we can extremely quickly find an element at an index -- so if we maintain a second list, we can do the opposite.
whereIsIt = {} tab = {} function addTo(val) local i = #tab + 1 tab[i] = val whereIsIt = val end function remove(val) tab[whereIsIt[val]] = nil whereIsIt[val] = nil end
Mathematically, hashtables are O(n^2)
. However, because of an extra memory usage, they are in practice (for anything within reason, in Lua even tens or hundreds of thousands of values) they are essentially O(1)
, meaning it ignores the number of elements -- the fastest solution. However, this is less performant in terms of memory -- both in its redundancy and in the inherent form of a hashtable.
With the above stated (hashtable) approach, you cannot efficiently maintain the table as a list -- it will have nil
gaps (in fact that #tab
will not be "accurate" (as in position of last element) however as it is used in addTo
will still yield correct results)