This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
leftler's profile picture

Sorting a table with a string key

Started by leftler, 26 March 2013 - 06:47 PM
leftler #1
Posted 26 March 2013 - 07:47 PM
I am attempting to sort a table by a sub sub-table, however it seems that the comparison function i pass in to "table.sort" is never called. This is for a high score system, i wanted to write it so i could call scores["playerName"]["bestTime"] but when I attempt to sort the list to print it in a top 10 list It does not display correctly.

Here is a simplified version of what i am doing,


local testString = "{[\"leftler\"]={[\"tries\"]=1,[\"bestTime\"]=1.000,},[\"leftler2\"]={[\"tries\"]=2,[\"bestTime\"]=3.000,},[\"leftler3\"]={[\"tries\"]=3,[\"bestTime\"]=2.000,},}"
local testTable = textutils.unserialize(testString)


function compareTimes(player1, player2)
  print("sorting")
  player1Time = player1["bestTime"]
  player2Time = player1["bestTime"]

  --if the player does not have a best time use the math.huge value for sorting
  if(player1Time == nil) then
	player1Time = math.huge
  end
  if(player2Time == nil) then
	player2Time = math.huge
  end

  print("time1: "..player1Time..", time2: "..player2Time)
  return player1Time < player2Time
end

function formatTime(minecraftTime)
  local realSeconds = minecraftTime * 50
  local totalHours, totalMin, totalSec
	
  totalHours = realSeconds / 60 / 60
  totalMin = (realSeconds / 60) % 60
  totalSec = realSeconds % 60
	
  return totalHours, totalMin, totalSec
end

table.sort(testTable, compareTimes)

local currentIndex = nil
local done = false
for i=1, 10 do
  currentIndex = next(testTable,currentIndex)
  if(currentIndex ~= nil and done == false) then
	local hr, mn, sec = formatTime(testTable[currentIndex]["bestTime"])
	print(string.format("%2d) %s\t%d:%02d:%02d", i, currentIndex, hr, mn, sec))
  else
	done = true
	fillLine(" ")		
  end
end

if everything worked correctly I should get

1) leftler 0:00:50
2) leftler3 0:01:40
3) leftler2 0:02:40
however my code is outputing

1) leftler3 0:01:40
2) leftler 0:00:50
3) leftler2 0:02:40

My initial guess is LUA tables act like C# dictionaries, so order is not guaranteed. If that is so what is a good design pattern so I can do quick updates but also sort out a top 10 list based on a sub-table?

(pastebin of real program http://pastebin.com/DcGiEwYd)
faubiguy #2
Posted 26 March 2013 - 08:13 PM
Yes, table string keys have no ordering. I changed it to use numbered indexes, which allow the table to be sorted, and made the player name a value in the tables along with tries and bestTime. Updated code:
local testString = "{[\"leftler\"]={[\"tries\"]=1,[\"bestTime\"]=1.000,},[\"leftler2\"]={[\"tries\"]=2,[\"bestTime\"]=3.000,},[\"leftler3\"]={[\"tries\"]=3,[\"bestTime\"]=2.000,},}"
local testTable = {
  [1]={ -- Number indexes are ordered, string keys are not
	tries=1,
	bestTime=1.000,
	name="leftler", -- I put player name in table as value
  },
  [2]={
	tries=2,
	bestTime=3.000,
	name="leftler2",
  },
  [3]={
	tries=3,
	bestTime=2.000,
	name="leftler3"
  },
}


function compareTimes(player1, player2)
  print("sorting")
  player1Time = player1["bestTime"]
  player2Time = player2["bestTime"] -- Corrected player1 to player2

  --if the player does not have a best time use the math.huge value for sorting
  if(player1Time == nil) then
		player1Time = math.huge
  end
  if(player2Time == nil) then
		player2Time = math.huge
  end

  print("time1: "..player1Time..", time2: "..player2Time)
  return player1Time < player2Time
end

function formatTime(minecraftTime)
  local realSeconds = minecraftTime * 50
  local totalHours, totalMin, totalSec

  totalHours = realSeconds / 60 / 60
  totalMin = (realSeconds / 60) % 60
  totalSec = realSeconds % 60

  return totalHours, totalMin, totalSec
end

table.sort(testTable, compareTimes) -- Made table be set to sorted table

local currentIndex = nil
local done = false
for i=1, 10 do
  --currentIndex = next(testTable,currentIndex)
  if(testTable[i] ~= nil and done == false) then
		local hr, mn, sec = formatTime(testTable[i]["bestTime"])
		print(string.format("%2d) %s\t%d:%02d:%02d", i, testTable[i].name, hr, mn, sec))
  else
		done = true
		--fillLine(" ")		  
  end
end
leftler #3
Posted 27 March 2013 - 10:47 AM
Now that you made it numericly indexed, when I get a report from a Player Detector from Misc Perf. how do I know which index to update? Before I was just doing "scores[playerName]["bestTime"] = timeValue". Will i need to keep a 2nd table with the name to index mappings, and if so, how is the best way to keep the indexes in sync after sorting the best times?

Would making a 2nd table (numerical index based) and copying over just the player names and times (basicly the schema you are using right now) and just updateing that table every time I need to update the scoreboard be a better approch?