Comparing two tables.

Posted by Falgor on Sun 29 Jan 2017 10:05 PM — 13 posts, 41,255 views.

#0
I have two tables, I want to make a code to return the differences in them (in a string, ideally).


a = {
     1 = cat
     2 = dog
     3 = bear
     4 = beaver
     }

b = {
     1 = cat
     2 = dog
     3 = bear
     4 = beaver
     5 = ox
     }

I want to the function to return key 5 from table B into a string.
USA Global Moderator #1
You're very unspecific about whether you care about ordering of the result or how you want it to look, so here's one approach of many to get you started:

[EDIT]I've edited this to show that it works

a = {
     "cat",
     "dog",
     "elephant",
     "bear",
     "beaver"
     }

b = {
     "cat",
     "dog",
     "bear",
     "beaver",
     "ox"
     }

a_inverse = {}
b_inverse = {}
differences = {}

for k,v in ipairs(a) do
   a_inverse[v] = k
end

for k,v in ipairs(b) do
   if a_inverse[v] == nil then
      print(v,"is only in table b")
   end
   b_inverse[v] = k
end

> ox     is only in table b

for k,v in ipairs(a) do
   if b_inverse[v] == nil then
      print(v,"is only in table a")
   end
end

> elephant     is only in table a
Amended on Mon 30 Jan 2017 07:36 PM by Fiendish
#2
This works perfectly thank.

All I'm going to do now is add an if function to show output if something has changed.


if differences == nil then
else
tprint (differences)


I'll have a crack at it when I get in from work.
USA Global Moderator #3
Since differences is defined already it will never be nil. You'll want


if #differences ~= 0 then
   tprint(differences)
end
Amended on Mon 30 Jan 2017 10:11 AM by Fiendish
#4
Meh, I'm tying myself in knots here. I'm not even sure this is the best way to approach this.

What I'm making is a log on/log off function that compares 2 lists, and prints differences:


Bear has logged in.

Bear has logged out.


Would I be better off making 2 strings and removing the duplicates from those, rather than messing around with tables? I just wanted to use tables because they are slightly more efficient and seem the 'right' way to do things.
#5
for k,v in ipairs (wholist) do
a_inverse[v] = k
end

for k,v in ipairs (oldwholist) do
if a_inverse[v] == nil then
print(v .. " logged out.")
end
b_inverse[v] = k
end

for k,v in ipairs (wholist) do
if b_inverse[v] == nil then
print(v .. " logged in.")
end
end


So i'm nearly there! This works one way, but not the other. It is displaying "logged in" messages, which means it is successfully finding the difference when a new value is added to the "wholist" table, but doesn't show my "logged out" messages (when wholist loses a value).
USA Global Moderator #6
Perhaps the problem is your lists. I've edited my previous post to show that it does work in both directions.
#7
Perhaps it is because my list has numerical keys?

I make my list like so


for player in string.gmatch(page, "title...(%w+)")
do
wholist [ i ] = "" .. player
i = i + 1
end


therefore my "wholist" table looks like this


1="Bear"
2="Beaver"
3="Ox"
4="Elephant


At the very start of my code (before the above function) I do the following to populate the "oldwholist" table


oldwholist = {}
oldwholist = wholist


Therefore by the time I come to compare the tables I have them both populated, and if someone logs out, they will be present on "oldwholist" but not on "wholist". When someone logs in, the reverse is true, I think my logic is sound. Just trying to unlearn Zuggsofts scripting language, I've relied too heavily on FORALL functions :)
Amended on Mon 30 Jan 2017 07:50 PM by Falgor
USA Global Moderator #8
Falgor said:

Perhaps it is because my list has numerical keys?


All lists have numeric keys unless otherwise specified.

{"bear", "dog", "pig"}
is the same as
{[1]="bear", [2]="dog", [3]="pig"}
USA Global Moderator #9
Falgor said:

At the very start of my code (before the above function) I do the following to populate the "oldwholist" table


oldwholist = {}
oldwholist = wholist



This is likely your problem. Table variables in Lua are only references, meaning they only point to some location in memory and are not the table objects themselves. There is some table object somewhere in memory, but your wholist variable isn't actually the table. It's just a memory address.
So you think you're cloning the table there, but you're not. All you're doing is making the oldwholist variable point at the same location in memory as the wholist variable. That doesn't duplicate. That makes them the same and any future actions will modify both variables.

Try this to demonstrate the problem for yourself:

a = {"a","b","c"}
b = {"d","e","f"}

for i,v in ipairs(a) do
   print(i,v)
end
for i,v in ipairs(b) do
   print(i,v)
end

a = b

print(a)
print(b)

a[2] = "HELLO"

for i,v in ipairs(a) do
   print(i,v)
end
for i,v in ipairs(b) do
   print(i,v)
end




Try this instead...


require "copytable" -- table copying module included with MUSHclient
oldwholist = copytable.deep(wholist)
Amended on Mon 30 Jan 2017 08:10 PM by Fiendish
#10
Okay. Here is the complete code and a break down of how it works. I've modified how "oldwholist" gets its content to make sure that both tables are working how they should.

I know a lot of this should be in separate functions, but I'm very new to Lua so getting it working first before I neaten it all up and put it in a plugin.

Firstly, I grab the data from my MUD's website to get it ready to break down into the 'wholist' table.


if not http then http = require("socket.http") end
local mysocket = require("socket.http")
mysocket.TIMEOUT = 2
local page = http.request("http://www.t2tmud.org/who.php") 


Next I use gmatch to strip out the names of the online Players from all the API data, these names are put into the "wholist" table.


local player = {}
i = 1
for player in string.gmatch(page, "title...(%w+)")
do
wholist [ i ] = "" .. player
i = i + 1
end


I then run the ipairs/foreach/if functions to see if "wholist" is different to "oldwholist", to see if someone has logged in or out, changes are sent to Output. I'm aware this won't work the first time the script is run, as it needs to run twice to populate both "wholist" and "oldwholist".


for k,v in ipairs (wholist) do
a_inverse[v] = k
end

for k,v in ipairs (oldwholist) do
if a_inverse[v] == nil then
print(v .. " logged out.")
end
b_inverse[v] = k
end

for k,v in ipairs (wholist) do
if b_inverse[v] == nil then
print(v .. " logged in.")
end
end


Next I use the same function as earlier to populate "oldwholist" from the websites data, this means that on the next cycle, "wholist" will be updated before "oldwholist", allowing the above comparing function to work.


i = 1
for player in string.gmatch(page, "title...(%w+)")
do
oldwholist [ i ] = "" .. player
i = i + 1
end


Finally I turn the "wholist" table into a string and send it to a miniwindow.


wholistfinal = table.concat (wholist, ",")
win = "win_" .. GetPluginID ()
WindowCreate (win, 0, 0, 2000, 33, miniwin.pos_top_left, 0, ColourNameToRGB("navy"))
WindowFont (win, "f", "Courier New", 10, true, false, false, false)
WindowText (win, "f", wholistfinal,
            2, 0, 0, 0, 
            ColourNameToRGB ("white"), 
            false)
WindowShow (win, true)



About 5% of the time, I get the correct log on/off messages, despite the miniwindow changing correctly each time someone logs in or out. I'm currently running this in a timer, it will end up in a loop in a plugin. Also the [ i ] aren't typos, they where turning everything italic.

Thanks for any help or tips to help me neaten the whole code up.
Amended on Mon 30 Jan 2017 09:17 PM by Falgor
USA Global Moderator #11
I'll test later if you don't manage to get it working. In the meantime, though...

why do you do
"" .. player
?

Also, do you empty your lists before repopulating them?
#12
I literally solved it as you posted that!

I've been messing around so much with populating the "oldwholist", I totally forgot that I removed to line to clear the "wholist".

Works perfectly!

Thanks so much, now it's time to turn each bit into a separate function so it looks smoother and get it sent to a plugin!

(once I figure out miniwindow wordwrapping!)