Sorting tables by heading name

Posted by Tiopon on Sat 24 Apr 2010 09:11 PM — 18 posts, 70,533 views.

USA #0
So my current problem is that my huge table is getting very difficult to see things in. I've tried to sort it using information from the following posts:
http://www.mushclient.com/forum/bbshowpost.php?id=6036
http://www.mushclient.com/forum/bbshowpost.php?id=8491
http://www.mushclient.com/forum/bbshowpost.php?id=8608
http://www.mushclient.com/forum/bbshowpost.php?id=5643

The table itself, printed with tprint, looks sort of like this:
Quote:
1:
"name"="Relay"
"finish1"="Handoff"
"attack1"="Point"
"level"=1
2:
"name"="Busboy"
"finish1"="Ringup"
"attack1"="Clean"
"level"=1
As you can see, I'm using numbers as the main identifier for the bits (table[1], table[2], etc). I want it to sort by the actual names inside the table though, so I want it to look like:
Quote:
1:
"attack1"="Point"
"finish1"="Handoff"
"level"=1
"name"="Relay"
2:
"attack1"="Clean"
"finish1"="Ringup"
"level"=1
"name"="Busboy"
So I just want it alphabetically sorted on each inner entry... I tried doing table.sort(table[1]) and that didn't change anything... table.sort(table[1], alphanum(table[1])) also runs, but doesn't seem to change anything. I know there's some sort of thing involving making a new table to sort it, but when I ran this:
Quote:
/table2 = {} table.foreach (table, function (k) table.insert (table2, k) end) table.sort(table2) tprint(table2)

all that did for me was the following...
Quote:
1=1
2=2
which isn't especially useful. :) It appears that's the way it's supposed to be working, but what I want it to do is to grab the attack1/finish1/etc, and update the original table... if that means I need to make a temporary table that's been sorted, do table[1] = temptable, temptable = {} between each, that works... I just want it to work and be readable as opposed to the painful mess it's becoming with 269 lines in it currently, sorted only by whatever internal logic determines initial placement.
Amended on Sat 24 Apr 2010 09:18 PM by Tiopon
USA #1
I can see that if I do the following instead:
Quote:
/table2 = {} table.foreach (table[1], function (k) table.insert (table2, k) end) table.sort(table2) tprint(table2)
I get a sorted set of the headings like this:
Quote:
1="attack1"
2="finish1"
3="level"
4="name"
Next question... how do I get that back into the main table, and iterate the sorting over the table[1] through table[45] in a less painful way... I'm guessing it'll be a table.foreach done on the table itself... but... I'm so confused on how to get it back. I did search the forum and didn't find anything that seemed to be regarding this directly before I posted... :(

Edit: Trying this does work, but doesn't do anything because the numbers are already sorted. Just displays the whole list again with 'key' instead of the number, and takes out the indenting.
Quote:
/table2 = {} table.foreach (table, function (k) table.insert (table2, k) end) table.sort(table2) for k,v in ipairs (table2) do print ("key =",v) table.foreach(table[v], print) end
but if I try to sort one of the actual fields, like this:
Quote:
/table2 = {} table.foreach (table[1], function (k) table.insert (table2, k) end) table.sort(table2) for k,v in ipairs (table2) do print ("key =",v) table.foreach(table[1][v], print) end
I get the following error:
Quote:
[string "Command line"]:1: bad argument #1 to 'foreach' (table expected, got string)
If I understand properly, this means that it's not actually able to do the sorting itself again, right? Because if I do have it doing the lookup properly, I could have it saving the values to a temporary third table, then overwrite the first table segment with the third table... or is there a better way to do this? *sighs*
Amended on Sat 24 Apr 2010 09:39 PM by Tiopon
USA #2
This should do everything you need.

table.sort(tbl, function(t1, t2)
  return t1.name < t2.name
end)


I'm sure you'd have gotten it with a bit more research and testing though. :)
USA #3
So how exactly how am I supposed to use that... like this?
Quote:
table.sort(table[1], function(t1,t2) return t1.name < t2.name end)
or
Quote:
table.sort(table, function(t1,t2) return t1.name < t2.name end)

Am I supposed to do the table2 thing first? Sorry, it's just not seeming to do anything... I'm just not properly comprehending how the process is working.
Australia Forum Administrator #4
The module pairsbykeys which ships with MUSHclient will do that. Given a table, it iterates through it in the order of the keys (so attack1 would come first):


require "pairsbykeys"

-- This prints the math functions in key order
for k, v in pairsByKeys (math) do
  print (k, v)
end -- for


(Internally it copies the keys into a temporary table, sorts that, and then returns the items one by one).
Amended on Sat 24 Apr 2010 10:38 PM by Nick Gammon
USA #5
Tiopon said:

So how exactly how am I supposed to use that... like this?
Quote:
table.sort(table[1], function(t1,t2) return t1.name < t2.name end)
or
Quote:
table.sort(table, function(t1,t2) return t1.name < t2.name end)

Am I supposed to do the table2 thing first? Sorry, it's just not seeming to do anything... I'm just not properly comprehending how the process is working.


The second one. That function you pass to table.sort is called a comparator, and it's called to compare two values within the table, to decide which comes first. In your table, each value is a table itself, and when two of them are compared, they're passed to the comparator. In the comparator, I determine the order based on each one's name,


@Nick: I may have misunderstood Tiopon, but I believe he wants to sort by value, not key.
Amended on Sat 24 Apr 2010 11:05 PM by Twisol
USA #6
Heh. Just realized now what Twisol's suggestion did... resorted my table by name and losing its key sorting, which would be fine if the numbers weren't directly related to the table. :p Time to fix 268 rows in Excel... Done.

Ah, thought that pairsByKeys might be the right one... So here's what I did to actually have it display an individual entry:
Quote:
/for k,v in pairsByKeys(table[1]) do print (k,v) end


What I'm not quite sure about is how to get that to update the original table properly and not just display it beautifully... I suppose I can just leave it messy in reality and show it nicely when done, but what do I have to do to make that work... I was trying to come up with a for ipairs that would let me do all the tables, but failing. Now that my table is fixed up again and I have an Excel backup for if I screw it up more with testing, let's see what we can do... heh.

Edit: Twisol, your suggestion would have worked great if I wanted was to change
Quote:
1:
"attack1"="Point"
"finish1"="Handoff"
"level"=1
"name"="Relay"
2:
"attack1"="Clean"
"finish1"="Ringup"
"level"=1
"name"="Busboy"
into
Quote:
1:
"attack1"="Clean"
"finish1"="Ringup"
"level"=1
"name"="Busboy"
2:
"attack1"="Point"
"finish1"="Handoff"
"level"=1
"name"="Relay"
but I was trying to sort the attack1/finish/level/name, not based on the "name" field. :)

So this will display the whole listing... there's 45 subtables under the main one, but if I can make it not be hardcoded that would be nice... be nicer if I can have it update the table itself. :)
Quote:
/for i=1,45 do for k,v in pairsByKeys(table) do print (k,v) end end
Amended on Sat 24 Apr 2010 11:30 PM by Tiopon
USA #7
I see, I did misunderstand. No, you cannot sort the non-numeric keys in a table. Lua tables are basically hashmaps or associative arrays. Pairs are inherently unsorted, and the only way to actually sort by anything else is exactly what you found: turning a=b into 1={a,b}.

There is generally no reason to sort by key unless you're going to use that order immediately, in which case Nick's solution would work excellently.
Amended on Sat 24 Apr 2010 11:29 PM by Twisol
Australia Forum Administrator #8
You can't "update the original table properly" - if you use named keys for tables in Lua they are stored by hash and therefore retrieved in what seems a random fashion.

Only numeric keys (1, 2, 3 etc) are retrieved in sequential order, and only then if you use ipairs rather than pairs.

The only thing you can do is sort them upon retrieval (which is very fast).

It's not really "messy" - the way the data is stored internally isn't a big concern, the important thing is to present it nicely when required.
USA #9
Is there a good way to save it into a new table using the pairsByKeys function? If I can save it into a new table, I should be able to use that temporary table to replace the new table when it's done... just parse through the tables twice, once to sort, once to replace. I suppose a third time to wipe the temporary tables.
USA #10
But see, what you are asking is inherently impossible. You can't reorder non-numeric keys. The order you get pairs from pairs() has no.discernable order, and indeed isn't supposed to have an order. It simply returns the pairs in whatever order it finds them in.

ipairs() only gets around this by literally using a loop starting at 1 and going up to the first nonexistent entry.
Amended on Sat 24 Apr 2010 11:38 PM by Twisol
USA #11
But if I can display it correctly, I should be able to, instead of making it display, save it to a new table, right? Should be able to do something like (but not exactly):
Quote:
/table2 = {} for j=1,45 do table2[j] = {} for k,v in pairsByKeys(table[j]) do table2[j][k]=v end end


If I have it create a second table, I can then save the second table instead of the first, and next time it loads up the table on a restart I'll have a nicely sorted table (until the next time it updates). Also, I'll be able to use tprint on the second table to have everything nicely indented. :)

Edit: So I suppose using i as my value wasn't the best choice, since it gets grabbed by the code as 'italicize'. Changing the i to j, so it doesn't get snagged.

Or is what you're saying that the code will save in random order based on its current hash, so regardless of any prior sorting, it's not going to be sorted when it's saved? :) Just realized now that might be what you were saying.
Amended on Sat 24 Apr 2010 11:47 PM by Tiopon
USA #12
Incorrect. Every insertion in inherently unsorted as well. This is why this code:

tbl = {}
tbl.a = 1
tbl.b = 2
tbl.c = 3
tprint(tbl)


results in (as just one possibility):

"b": 2
"a": 1
"c": 3


In response to your edit: Yes, that might be it. The order tprint comes up with for a table will always be the same no matter how many times you run it, but once you modify it, that all goes out the window.
Amended on Sat 24 Apr 2010 11:50 PM by Twisol
USA #13
So what I really need is an alias similar to the following...
Quote:
/for j=1,45 do print(j .. ":") for k,v in pairsByKeys(table[j]) do print(" \"" .. k .. "\"\=\"" .. v .. "\"") end end
... Which, at least as I try it now, appears the same as tprint, but is sorted alphabetically...

Easy enough. Copied the tprint function to tprinta and changed the 'value in pairs' to be 'value in pairsByKeys'... everything else the same (well, besides renaming tprint to tprinta everywhere in it so the functions don't fight) and tprinta gives me my nicely sorted lists. :) tprinta follows:
--
--  tprinta.lua

--[[

For debugging what tables have in them, prints recursively

See forum thread:  http://www.gammon.com.au/forum/?id=4903

eg.

require "tprinta"

   tprinta (GetStyleInfo (20))

--]]

require "pairsbykeys"

function tprinta (t, indent, done)
  -- in case we run it standalone
  local Note = Note or print
  local Tell = Tell or io.write
  
  -- show strings differently to distinguish them from numbers
  local function show (val)
    if type (val) == "string" then
      return '"' .. val .. '"'
    else
      return tostring (val)
    end -- if
  end -- show
  -- entry point here
  done = done or {}
  indent = indent or 0
  for key, value in pairsByKeys (t) do
    Tell (string.rep (" ", indent)) -- indent it
    if type (value) == "table" and not done [value] then
      done [value] = true
      Note (show (key), ":");
      tprinta (value, indent + 2, done)
    else
      Tell (show (key), "=")
      print (show (value))
    end
  end
end

return tprinta
Amended on Sun 25 Apr 2010 12:06 AM by Tiopon
USA #14
Excellent :)


Nick, I'd actually suggest using pairsByKeys in tprint by default. It's certainly very useful for inspecting tables if you can look at things in a natural order. Otherwise, a tprinta like above would be a welcome addition to tprint.lua.
USA #15
One slight change... I did put the 'require "pairsbykeys"' into the code above, so that people who don't realize that they need to enable that as well will still have it working.
Australia Forum Administrator #16
I don't see a big problem with making that the default (except perhaps, to confuse people because that will now NOT be the order they will get them programmatically) so I have amended the released version.

Commit 039e557. Edit: Reverted.
Amended on Sun 25 Apr 2010 01:15 AM by Nick Gammon
Australia Forum Administrator #17
Ah well that will teach me to not think things through. :-)

I had to revert that change.

Your idea is fine for string keys. However tprint prints generic objects, so the table might consist of (say) functions, strings, booleans, and numbers (as table keys).

It is not meaningful to compare a function to a string, or indeed a function to a function, and thus they can't be sorted.

Your suggested change will only work for a subset of all possible tables.