Lua doesn't have a "remove this value" function, probably because there's a few ambiguities in what that would mean:
- Remove one (which?) or all of that value?
- Remove by just setting to
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).
Linear Search
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)
Binary Search
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)
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
Hashtable
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)
2 comments
Thanks, that was a really good quality answer! You say that you can't efficiently maintain a table as a list if you need to interact with it, so are there any alternatives you suggest?
If you are using a hashtable, simply "dealing with it" is probably the best way to go -- you can still use
pairsto loop over all values, it will just be skipping indexes. However, again, I think the linear approach is probably what you should go with -- what is the data that you are going over?