I have a list of indexes I need my users to input, and I need the code to check if any of them are repeated, so it would give an error (they cannot be repeat).
if i only had two indexes it would be simple as :
if indexa == indexb then error() end
but its a fairly long list.
Here's a basic algorithm for detecting repeats.
-- This table is what's known as a set.
local indexes = {}
while true do
local index = getIndexFromUser()
-- Check for end of input.
if not index then
break
end
-- Check for repeats.
if indexes[index] then
error()
end
-- Store index as a key in indexes.
indexes[index] = true
end
In other words, table keys cannot be repeated, so you can simply store any non-nil value in a table under that key. Later (in future iterations of the loop), you can check to see whether that key is nil.
You could put all the indices in a table, use table.sort to sort them, then loop over table items to test if any consecutive items are identical:
indices = {1,6,3,0,3,5} -- will raise error
indices = {1,6,3,0,4,5} -- will not raise error
table.sort(indices)
for i=1, (#indices-1) do
if indices[i] == indices[i+1] then
error('not allowed duplicates')
end
end
Related
I am pretty new to lua so please excuse me if this question is too basic. I was wondering if there is a good way to check if a value is not in a lua table. Something like:
if 5 ~= t[1] or 5 ~= t[2] or 5 ~= t[3] ... then end
but less stupid.
This
for i,v in ipairs(t) do
if 5 ~= v then
end
end
does not really work because I want to check if it does not show up anywhere in the table rather than if it equals to any given value.
Probably the only somewhat viable solution I could think of so far would be something like
check = 0
for i,v in ipairs(t) do
if 5 == v then
check = 1
end
end
if check == 0 then end
but that still looks kind of cumbersome...
Thanks so much!
If you need to check whether an item exists in array, you better keep a hash map to that array. This is obviously ineffective to go through the whole (or part of the) array for each check. I'll suggest you to create the mapping and only after, do your checks. Example:
local function array_map(array)
local map = {}
for _, item in ipairs(array) do
map[item] = true
end
return map
end
local array = {1,2,3,4,5,6,7}
local arr_map = array_map(array)
if arr_map[1] then
print("The array has item 1")
end
if not arr_map[10] then
print("Item 10 is not part of the array")
end
This is how you get your tests in constant time of ϑ(1) + n once for the map build.
If you want to know if a value is in a table you have to compare every table value to your value until you find your first match.
for k,v in pairs(myTable) do
if v == searchValue then
print("Found one!")
break
end
end
Keep in mind that ipairs only works for tables with consecutive indices 1-n.
If you want to check any table elements use pairs.
I have this table local cookies = {{["name"]=23, ["value"]=333}, {["name"]=222, ["value"]=33233}} and I want to iterate over the subtables to find the one with the correct "name". Here is what I have tried
for _,elm in ipairs(cookies) do
for k,v in ipairs(elm) do
print(k)
if k == "name" and v == 222 then
print(v)
end
end
end
I does show in the outer for loop that it sees to tables, however, it does not even enter the inner for loop - why? How can I find the subtable for which "name" equals a certain value?
ipairs only iterates over the keys 1, 2, 3, ..., so it won't visit the key "name". If you want to visit all keys, use pairs (though be warned the order of iteration is not predictable).
However, for your example you don't need an inner loop at all. You can simple get the name of elm as elm.name:
for _,elm in ipairs(cookies) do
if elm.name == "222" then
print(elm.name, elm.value)
end
end
In fact, if you don't need the ordering or need to support duplicated cookie names, your cookies table could become a dictionary of name => value, allowing you to write this with no loops:
print(cookies["222"]) --> 33233
if FirstName:GetValue() == ""
or table.HasValue( Blocked, string.lower(FirstName:GetValue())) then
-- Checks if the name contains any "bad" words, or nothing was typed in.
FirstNameCHECK = false
text4:SetText("Bad Name")
text4:SetColor(Color(255,0,0,255))
else
FirstNameCHECK = true
text4:SetText("Good Name")
text4:SetColor(Color(0,255,0,255))
end
This code currently checks for the string being exactly the same as an entry in a table.
How would I be able to change this code so it checks if the string inserted (FirstName variable) contains one of the entries from the table?
The inefficient solution would be to iterate over the table and call string.find on each element. This approach can get quite slow for very large tables, as you have to inspect every element, but will probably be perfectly fine unless you are dealing with really big datasets.
A more clever approach would be to use nested tables indexed by substrings. For instance, you could have a root table with indices a to z. You index into that table with the first letter of your word and you get back another table of the same structure. This one you index with the second letter and so on until you either find no more table at the letter you are checking, or you arrived at the end of the word. In the latter case, you might need an additional unique entry in the table that indicates whether the exact word you were looking up is in the table (as there are no more letters, you need to be able to check this somehow).
This approach has several disadvantages. Building the table can be very memory intensive and performing the check might very well be slower than the naive approach for smaller tables. Also manipulating the lookup table is not that straightforward (think about removing a word from it for instance). So this kind of structure is really only useful for performing the lookup and you should stick with normal tables for other tasks.
For example, you might want to maintain in the lookup table a list of references to entries in the real data table which allow you to get from a particular prefix string all matching entries in the data table.
Made a solution for those kind of stuff in some other question (works with multi dimensional tables) - Loop until find 2 specific values in a table?
function MultiTableSearch(input,value,case,index_check)
if (input and type(input) == 'table') then
if (type(value) == 'table' and value == input) then
return true;
end
for key,object in pairs(input) do
if (index_check) then
if (case and type(input)=='string' and type(key)=='string') then
if (value:lower() == key:lower()) then -- to avoid exit the loop
return true;
end
else
if (key == value) then
return true
elseif(type(object)=='table') then
return MultiTableSearch(object,value,case,index_check)
end
end
else
if (case and type(value)=='string' and type(object) == 'string') then
if (value:lower() == object:lower()) then
return true;
end
elseif(type(object)=='table') then
if (value == object) then
return true;
else
return MultiTableSearch(object,value,case)
end
else
if (object == value) then
return true;
end
end
end
end
end
return false;
end
and to use call this
MultiTableSearch(blocked,FirstName:GetValue(),true) -- table,name,case(true to ignore case)
Is there a method for checking if a table contains a value ? I have my own (naive) function, but I was wondering if something "official" exists for that ? Or something more efficient...
function table.contains(table, element)
for _, value in pairs(table) do
if value == element then
return true
end
end
return false
end
By the way, the main reason I'm using this functions is to use tables as sets, ie with no duplicate elements. Is there something else I could use ?
You can put the values as the table's keys. For example:
function addToSet(set, key)
set[key] = true
end
function removeFromSet(set, key)
set[key] = nil
end
function setContains(set, key)
return set[key] ~= nil
end
There's a more fully-featured example here.
Given your representation, your function is as efficient as can be done. Of course, as noted by others (and as practiced in languages older than Lua), the solution to your real problem is to change representation. When you have tables and you want sets, you turn tables into sets by using the set element as the key and true as the value. +1 to interjay.
I know this is an old post, but I wanted to add something for posterity.
The simple way of handling the issue that you have is to make another table, of value to key.
ie. you have 2 tables that have the same value, one pointing one direction, one pointing the other.
function addValue(key, value)
if (value == nil) then
removeKey(key)
return
end
_primaryTable[key] = value
_secodaryTable[value] = key
end
function removeKey(key)
local value = _primaryTable[key]
if (value == nil) then
return
end
_primaryTable[key] = nil
_secondaryTable[value] = nil
end
function getValue(key)
return _primaryTable[key]
end
function containsValue(value)
return _secondaryTable[value] ~= nil
end
You can then query the new table to see if it has the key 'element'. This prevents the need to iterate through every value of the other table.
If it turns out that you can't actually use the 'element' as a key, because it's not a string for example, then add a checksum or tostring on it for example, and then use that as the key.
Why do you want to do this? If your tables are very large, the amount of time to iterate through every element will be significant, preventing you from doing it very often. The additional memory overhead will be relatively small, as it will be storing 2 pointers to the same object, rather than 2 copies of the same object.
If your tables are very small, then it will matter much less, infact it may even be faster to iterate than to have another map lookup.
The wording of the question however strongly suggests that you have a large number of items to deal with.
I can't think of another way to compare values, but if you use the element of the set as the key, you can set the value to anything other than nil. Then you get fast lookups without having to search the entire table.
-- in some helper module
function utils_Set(list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end
-- your table here
long_table = { "v1", "v2", "v1000"}
-- Consult some value
_set = utils_Set(long_table)
if _set["v1"] then print("yes!") end
I can't get table entry index. I need it to remove an item from table.
I use table.insert to add entries to table.
Another question: why Lua doesn't have "overload" to function table.remove so one can remove item by associative index?
Tables implement an unordered one to many relation between keys and values. In other words, any particular key (index) can only appear once in a table, but a value can appear multiple times.
If you know the key k, then t[k] = nil will remove both the key and the associated value from the table. However, this operation has no effect on any other keys or values in the table.
The table.insert and table.remove functions operate over the set of sequential integer keys beginning at 1, that are used by convention to implement arrays or lists. For that purpose, they manipulate other values in the list so as to keep the list from developing holes.
One way to find a key at which some value is found is to simply search the table. If this will be done more than once, then it is probably a good idea to build a second table that inverts the key/value pairs so that lookup by value is as fast as lookup by index.
A suitable implementation will depend on your assumptions and needs. Some samples are:
-- return the first integer index holding the value
function AnIndexOf(t,val)
for k,v in ipairs(t) do
if v == val then return k end
end
end
-- return any key holding the value
function AKeyOf(t,val)
for k,v in pairs(t) do
if v == val then return k end
end
end
-- return all keys holding the value
function AllKeysOf(t,val)
local s={}
for k,v in pairs(t) do
if v == val then s[#s+1] = k end
end
return s
end
-- invert a table so that each value is the key holding one key to that value
-- in the original table.
function Invert(t)
local i={}
for k,v in pairs(t) do
i[v] = k
end
return i
end
t[k]=nil removes from t the entry with key k.
For the second question, the answer is that tables can have individual metatables.