Lua: Merge 2 strings collections in a case-insensitive manner - lua

I want to merge two strings collections in a case-insensitive manner:
string_collection1 = {"hello","buddy","world","ciao"}
string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"}
merged_string_collection = merge_case_insensitive(string_collection1,string_collection2) --> {"hello","buddy","world","holly","bye","ciao"}
Here's an attempt, but it does not work...
function merge_case_insensitive(t1,t2)
t3 = {}
for _,s1 in pairs(t1) do
for _,s2 in pairs(t2) do
if string.lower(s1) == string.lower(s2) then
t3[s1] = s1
end
end
end
t4 = {}
i = 1
for s,_ in pairs(t3) do
t4[i] = string.lower(s)
i = i + 1
end
return t4
end
string_collection1 = {"hello","buddy","world","ciao"}
string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"}
merged_string_collection = merge_case_insensitive(string_collection1,string_collection2)
for k,v in pairs(merged_string_collection) do print(k,v) end

It does not work because you use == to compare both strings which is case-sensitive.
You could do something like string.lower(s1) == string.lower(s2) to fix that.
Edit:
As you can't figure out the rest yourself, here's some code:
local t1 = {"hello","buddy","world","ciao"}
local t2 = {"Hello","Buddy","holly","Bye", "bYe"}
local aux_table = {}
local merged_table = {}
for k,v in pairs(t1) do
aux_table[v:lower()] = true
end
for k,v in pairs(t2) do
aux_table[v:lower()] = true
end
for k,v in pairs(aux_table) do
table.insert(merged_table, k)
end
merged_table now contains the lower case version of every word in both input tables.
Now pour that into a function that takes any number of input tables and you are done.
What we did here: we use the lower case version of every word in those tables and store them in a list. aux_table[string.lower("Hello")] will index the same value as aux_table[string.lower("hello")]. So we end up with one entry for each word, even if a word comes in multiple variations.
Using the keys saves us the hassle of comparing strings and distiguishing between unique words and others.

To get a table with all strings from two other tables appearing once (without regard to case), you need something like this:
function merge_case_insensitive(t1,t2)
local ans = {}
for _,v in pairs(t1) do ans[v:lower()] = true end
for _,v in pairs(t2) do ans[v:lower()] = true end
return ans
end
string_collection1 = {"hello","buddy","world","ciao"}
string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"}
merged_string_collection = merge_case_insensitive(string_collection1,string_collection2)
for k in pairs(merged_string_collection) do print(k) end
Edit: And in case you want an array result (without adding another iteration)
function merge_case_insensitive(t1,t2)
local ans = {}
local
function add(t)
for _,v in pairs(t) do
v = v:lower()
if ans[v] == nil then ans[#ans+1] = v end
ans[v] = true
end
end
add(t1)
add(t2)
return ans
end
string_collection1 = {"hello","buddy","world","ciao"}
string_collection2 = {"Hello","Buddy","holly","Bye", "bYe"}
merged_string_collection = merge_case_insensitive(string_collection1,string_collection2)
for _,v in ipairs(merged_string_collection) do print(v) end

We can do this by simply iterations over both tables, and storing a temporary dictionary for checking what words we have already found, and if not there yet, putting them in our new array:
function Merge(t1, t2)
local found = {} --Temporary dictionary
local new = {} --New array
local low --Value to store low versions of words in later
for i,v in ipairs(t1) do --Begin iterating over table one
low = v:lower()
if not found[low] then --If not found yet
new[#new+1] = low --Put it in the new table
found[low] = true --Add it to found
end
end
for i,v in ipairs(t2) do --Repeat with table 2
low = v:lower()
if not found[low] then
new[#new+1] = low
found[low] = true
end
end
return new --Return the new array
end
This method eliminates the need for a third iteration, like in Piglet's answer, and doesn't keep redefining a function and closure and calling them like in tonypdmtr's answer.

Related

Lua finding matching value's from 2 different tables and printing it

local Table1 = {"Test"}
local Table2 = {"'Test' = 'True'"}
--my friend showed me this
local Matched = {}
function has_same()
for _,v in pairs(Table1) do
for _,z in pairs(Table2) do
if v == z then table.insert(Matched, v)
end
end
end
end
has_same()
for i,v in pairs(Matched) do
print(v)
end
This is the code I've been trying to use and when I run it just comes up as blank
I am trying to make it so it prints the Test = True If this needs more explanation let me know

Count number of occurrences of a value in Lua table

Basically what I want to do is convert a table of this format
result={{id="abcd",dmg=1},{id="abcd",dmg=1},{id="abcd",dmg=1}}
to a table of this format:
result={{id="abcd",dmg=1, qty=3}}
so I need to know how many times does {id="abcd",dmg=1} occur in the table. Does anybody know a better way of doing this than just nested for loops?
result={{id="abcd",dmg=1},{id="defg",dmg=2},{id="abcd",dmg=1},{id="abcd",dmg=1}}
local t, old_result = {}, result
result = {}
for _, v in ipairs(old_result) do
local h = v.id..'\0'..v.dmg
v = t[h] or table.insert(result, v) or v
t[h], v.qty = v, (v.qty or 0) + 1
end
-- result: {{id="abcd",dmg=1,qty=3},{id="defg",dmg=2,qty=1}}
So you want to clear duplicate contents, although a better solution is to not let dupe contents in, here you go:
function Originals(parent)
local originals = {}
for i,object in ipairs(parent) do
for ii,orig in ipairs(originals) do
local dupe = true
for key, val in pairs(object) do
if val ~= orig[key] then
dupe = false
break
end
end
if not dupe then
originals[#originals+1] = object
end
end
return originals
end
I tried to make the code self explanatory, but the general idea is that it loops through and puts all the objects with new contents aside, and returns them after.
Warning: Code Untested

Remove duplicates from LUA Table by timestamp

I was on stack a few days back for help inserting records to prevent duplicates. However the process to enter these is slow and they slip in.
I have a user base of about 10,000 players, and they have duplicate entries.. I've been trying to filter out these duplicates without success. The examples on stack have no panned out for me.
Here is a clip from my table
[18] =
{
["soldAmount"] = 25,
["buyer"] = [[#playername]],
["timestampz"] = 1398004426,
["secsSinceEvent"] = 55051,
["guildName"] = [[TradingGuild]],
["eventType"] = 15,
["seller"] = [[#myname]],
},
[19] =
{
["soldAmount"] = 25,
["buyer"] = [[#playername]],
["timestampz"] = 1398004426,
["secsSinceEvent"] = 55051,
["guildName"] = [[TradingGuild]],
["eventType"] = 15,
["seller"] = [[#myname]],
},
The timestamp's match and they should not have been added.
for k,v in pairs(sellHistory) do mSavedTHVars.Forever_Sales[k] = v
if mSavedTHVars.Forever_Sales.timestampz ~= sellHistory.timestampz then
table.insert(mSavedTHVars.Forever_Sales, sellHistory)
end end
Now, I need to find out how to remove the current duplicates, and here is what I've tried.
function table_unique(tt)
local newtable = {}
for ii,xx in ipairs(tt) do
if table_count(newtable.timestampz, xx) ~= tt.timestampz then
newtable[#newtable+1] = xx
end
end
return newtable
end
I hope this information provided was clean and understandable.
Thanks!
UPDATE
Attempt #20 ;)
for k,v in pairs(mSavedTHVars.Forever_Sales) do
if v == mSavedTHVars.Forever_Sales.timestampz then
table.remove(mSavedTHVars.Forever_Sales,k)
end
end
No luck yet.
UPDATE
This has worked
for k,v in pairs(mSavedTHVars.Forever_Sales) do mSavedTHVars.Forever_Sales[k] = v
if v.timestampz == mSavedTHVars.Forever_Sales.timestampz then
table.remove(mSavedTHVars.Forever_Sales, k)
end
end
IS this a good approach?
Assuming that mSavedTHVars.Forever_Sales[18] and mSavedTHVars.Forever_Sales[19] are the tables you listed in your post, then to remove all duplicates based on same time stamp it is easiest to create a "set" based on timestamp (since the timestamp is your condition for uniqueness). Loop through your mSavedTHVars.Forever_Sales and for each item, add item to new table only if its timestamp not already in set:
function removeDuplicates(tbl)
local timestamps = {}
local newTable = {}
for index, record in ipairs(tbl) do
if timestamps[record.timestampz] == nil then
timestamps[record.timestampz] = 1
table.insert(newTable, record)
end
end
return newTable
end
mSavedTHVars.Forever_Sales = removeDuplicates(mSavedTHVars.Forever_Sales)
Update based on Question Update:
My comment on following proposed solution:
for k,v in pairs(mSavedTHVars.Forever_Sales) do
mSavedTHVars.Forever_Sales[k] = v
if v.timestampz == mSavedTHVars.Forever_Sales.timestampz then
table.remove(mSavedTHVars.Forever_Sales, k)
end
end
The problem is that I don't see how that can work. When you do for k,v in pairs(mSavedTHVars.Forever_Sales) do then v is mSavedTHVars.Forever_Sales[k] so the next line mSavedTHVars.Forever_Sales[k] = v does nothing. Then if v.timestampz == mSavedTHVars.Forever_Sales.timestampz compares the timestamp of v, i.e. of mSavedTHVars.Forever_Sales[k], with value of a timestampz field in mSavedTHVars.Forever_Sales. But latter is a table without such field, so right-hand-side of == will be nil, so the condition will only be true if v.timestampz is nil, which I don't think is ever the case.
The main reason that I used a solution of creating new table instead of removing duplicates from the existing table is that you can edit a table while iterating over it with pairs or ipairs. If you were to use a reverse counter, it would probably be ok (but I have not tested, test to be sure):
function removeDuplicates(tbl)
local timestamps = {}
local numItems = #tbl
for index=numItems, 1, -1, do
local record = tbl[index]
if timestamps[record.timestampz] ~= nil then
table.remove(newTable, index)
end
timestamps[record.timestampz] = 1
end
end
Also I think the intent of the function is not as clear, but maybe this is just personal preference.

How to get xth key of a table in Lua

I have 2 functions in Lua which create a dictionary table and allow to check if a word exists:
local dictTable = {}
local dictTableSize = 0
function buildDictionary()
local path = system.pathForFile("wordlist.txt")
local file = io.open( path, "r")
if file then
for line in file:lines() do
dictTable[line] = true
dictTableSize = dictTableSize + 1
end
io.close(file)
end
end
function checkWord(word)
if dictTable[word] then
return(true)
else
return(false)
end
end
Now I want to be able to generate a couple of random words. But since the words are the keys, how can I pick some, given the dictTableSize.
Thanks
Just add a numerical index for each word to the dictionary while loading it:
function buildDictionary()
local path = system.pathForFile("wordlist.txt")
local file = io.open( path, "r")
if file then
local index = 1
for line in file:lines() do
dictTable[line] = true
dictTable[index] = line
index = index + 1
end
io.close(file)
end
end
Now you can get a random word like this:
function randomWord()
return dictTable[math.random(1,#dictTable)]
end
Side note: nil evaluates to false in Lua conditionals, so you could write checkWord like this:
function checkWord(word)
return dictTable[word]
end
Another side note, you'll get less polution of the global namespace if you wrap the dictionary functionality into an object:
local dictionary = { words = {} }
function dictionary:load()
local path = system.pathForFile('wordlist.txt')
local file = io.open( path, 'r')
if file then
local index = 1
for line in file:lines() do
self.words[line] = true
self.words[index] = line
index = index + 1
end
io.close(file)
end
end
function dictionary:checkWord(word)
return self.words[word]
end
function dictionary:randomWord()
return self.words[math.random(1,#self.words)]
end
Then you can say:
dictionary:load()
dictionary:checkWord('foobar')
dictionary:randomWord()
Probably two ways: you can keep the array with words and just do words[math.random(#words)] when you need to pick a random word (just make sure that the second one is different from the first).
The other way is to use next the number of times you need:
function findNth(t, n)
local val = next(t)
for i = 2, n do val = next(t, val) end
return val
end
This will return b for findNth({a = true, b = true, c = true}, 3) (the order is undefined).
You can avoid repetitive scanning by memoizing the results (at this point you will be better off using the first way).
this is a trade off that you have for using the word table the way you are. i would invert the word table once you load it, so that you can get references to words by index as well if you have to. something like this:
-- mimic your dictionary structure
local t = {
["asdf"] = true, ["wer"] = true, ["iweir"] = true, ["erer"] = true
}
-- function to invert your word table
function invert(tbl)
local t = {}
for k,_ in pairs(tbl) do
table.insert(t, k)
end
return t
end
-- now the code to grab random words
local idx1, idx2 = math.random(dictTableSize), math.random(dictTableSize)
local new_t = invert(t)
local word1, word2 = new_t[idx1], new_t[idx2]
-- word1 and word2 now have random words from your 'dictTable'

"Transpose/Zip" function not working as expected

I am trying to build an elegant transpose function using functions mapn and zip in Lua.
The mapn and zip are as follows (From the lua book):
function map(func, array)
local new_array = {}
for i,v in ipairs(array) do
new_array[i] = func(v)
end
return new_array
end
function mapn(func, ...)
local new_array = {}
local i=1
local arg_length = table.getn(arg)
while true do
local arg_list = map(function(arr) return arr[i] end, arg)
if table.getn(arg_list) < arg_length then return new_array end
new_array[i] = func(unpack(arg_list))
i = i+1
end
end
These work as expected.
I then define zip and transpose as:
function zip(...)
return mapn(function(...) return {...} end,...)
end
function transpose(...)
return zip(unpack(...))
end
Now transpose({{1,2},{3,4},{5,6}}) produces {{1,3,5},{2,4,6}} as expected.
But transpose({{1,2},{3,4},{5}}) does not produce {{1,3,5},{2,4}}. It only produces one row.
How can I get it to produce the result I wish for?
I just decided to write an "inelegant" function instead. It seems there's no smooth way to use mapn and friends.
function transp(L)
local n=#L
local m,M=1e42,0
--Get the beginning and end of resultant transpose list.
for i=1,n do
for k,v in pairs(L[i]) do
if M<k then M=k end
if m>k then m=k end
end
end
local nt={}
for i=m,M do
local rt={}
for j=1,n do
rt[j]=L[j][i]
end
table.insert(nt,rt)
end
return nt
end
Please critique and improve this candidate solution.
I fixed a few things in your code and I think it works now as intended, I've added comments inline.
function map(func, array)
local new_array = {}
for i, v in ipairs(array) do
new_array[#new_array + 1] = func(v)
end
return new_array
end
function mapn(func, ...)
-- Variadic arguments bound to an array.
local arrays = {...}
local new_array = {}
-- Simple for-loop.
local i = 1
while true do
local arg_list = map(function(arr) return arr[i] end, arrays)
if #arg_list == 0 then
break
end
new_array[i] = func(unpack(arg_list))
i = i + 1
end
return new_array
end
-- Using 'mapn' instead of 'map' (probably how you intended).
function zip(...)
return mapn(function(...) return {...} end,...)
end
-- Same as before.
function transpose(...)
return zip(unpack(...))
end
Usage example:
for _, row in pairs(transpose({{1,2},{3,4},{5}})) do
for _, col in pairs(row) do io.write(col .. ' ') end
io.write('\n')
end
-- Output: 1 3 5
-- 2 4
The {5} in your example is being ignored because of this line:
if table.getn(arg_list) < arg_length then return new_array end
What you may want to do instead is break out of the loop only when arg_list is empty.
This will then give the result you want provided that the rows are monotonically increasing in length.
For the more general case, when later rows may be shorter than earlier ones
(e.g. {{1,2},{3,4,5},{6}}), you will need to keep track of the row lengths to allow for holes. This can be done by adding an optional argument (and extra return value) to map to indicate the maximum index i for which func(array[i]) was evaluated:
function map(func, array, len)
local new_array = {}
len = len or #array
for i=1,len do
new_array[i] = func(array[i])
end
return new_array, len
end
function mapn(func, ...)
local new_array = {}
local i=1
local arg_length = select('#', ...)
local args = {...}
while true do
local arg_list, num_results = map(function(arr) return arr[i] end, args, arg_length)
if not next(arg_list) then return new_array end
new_array[i] = func(unpack(arg_list, 1, num_results))
i = i+1
end
end
function zip(...)
return mapn(function(...) return {...} end,...)
end
function transpose(...)
return zip(unpack(...))
end

Resources