My table.sort is comparing values to nil that I never set - why?

Hi. I’ve got a dictionary of key/value pairs with the values being tables, and before presenting these key/values to the user I want to sort them in the order level->rarity->quantity->alphabetical. You naturally cannot sort dictionaries, so I’m converting them into tables and then sorting them that way.

It all works pretty smooth, but for unknown reasons when the size of my data dictionary gets to around 4 my table.sort function starts erroring stating that one of the tables it’s comparing is nil. If I never insert a nil table, why is this happening? It’s totally unexpected behaviour to me, but I’m probably messing up somewhere. I’ve spent the best part of today tackling this problem but I think I need help, I’m new to table sorting!

My data dictionaries look something like so:

local data = {
    ["Black"] = {lev1 = 0, lev2 = 1};
    ["Gold"] = {lev1 = 1, lev2 = 0};
    ["White"] = {lev1 = 1, lev2 = 1};
    ["Yellow"] = {lev1 = 1, lev2 = 0};
};

I then pass all of the data into my sorting function, which returns a table of colours, their quantity, and their level in a sorted manner. Just so I don’t miss anything, I’m going to paste it in it’s entirety. Sorry if it’s a bit much!

local function returnOrderedItems(objects, cat) -- {["Black"],["White"]}, "Claws"
	--[[data = {
	["Black"] = {lev1 = 0, lev2 = 1};
	["White"] = {lev1 = 1, lev2 = 0};
	}]]
	local toReturn = {}
	local Lev1 = {}
	local Lev2 = {}
	for key, value in pairs (objects) do
		if value.lev1 >= 1 then
			print(key.." has a level 1.")
			table.insert(Lev1,{colour = key, quantity = value.lev1, level = 1})
		end
		if value.lev2 >= 1 then
			print(key.." is a level 2.")
			table.insert(Lev2,{colour = key, quantity = value.lev2, level = 2})
		end
	end
	local function sortPls(lev)
		table.sort(lev, function(a, b)
			if a == nil then
				print("WHY IS TABLE SORTING AGAINST A NIL VALUE?")
			end
			if b == nil then
				print("WHY IS TABLE SORTING AGAINST B NIL")
			end
			local clr1 = a.colour
			local clr2 = b.colour
			local ranks = definitions[cat] --[claws] for example.
			local rank1 = 0
			local rank2 = 0
			for i, v in pairs (ranks) do
				if v[clr1] ~= nil then
					if i == "Common" then
						rank1 = 1
					elseif i == "Uncommon" then
						rank1 = 2
					elseif i == "Rare" then
						rank1 = 3
					elseif i == "Legendary" then
						rank1 = 4
					end
				end
				if v[clr2] ~= nil then
					if i == "Common" then
						rank2 = 1
					elseif i == "Uncommon" then
						rank2 = 2
					elseif i == "Rare" then
						rank2 = 3
					elseif i == "Legendary" then
						rank2 = 4
					end
				end
				if rank1 ~= 0 and rank2 ~= 0 then break end
			end
			if rank1 > rank2 then print(clr1.." is a higher rank than "..clr2) return a elseif rank2 > rank1 then print(clr2.." is a higher rank than "..clr1) return b end
			if a.quantity > b.quantity then print("Player owns more of "..clr1) return a elseif b.quantity > a.quantity then print("Player owns more of "..clr2) return b end
			print("Both colours are at the same rank and same quantity!")
			return a.colour<b.colour -- alphabetical
		end)
	end
	sortPls(Lev1)
	sortPls(Lev2)
	for i, v in pairs (Lev2) do
		table.insert(toReturn, v)
	end
	for i, v in pairs (Lev1) do
		if i == 1 then
			print("THE FIRST KEY OF lev1 SHOUDL BE THE HIGHEST :(")
			print(v.key)
		end
		table.insert(toReturn, v)
	end
	return toReturn
end

The annoying thing is, I’m getting this output:

WHY IS TABLE SORTING AGAINST B NIL

This is then promptly followed by:
attempt to index nil with 'colour'
When setting the variable ‘clr2’ to ‘b.colour’. Because b is nil. But why is a nil value even there in the first place? Why has my table sort function just pulled it out of thin air?

Help!

It looks like the problem is because you are doubling up unintentionally on your brackets.

Screen Shot 2020-05-26 at 10.58.57 AM

Are you trying to sort a dictionary? That doesn’t really make sense. Dictionaries are inherently unsorted, since the keys are, well, the keys, and not just arbitrary numbers.

2 Likes

posatta is correct. You can’t sort dictionaries like this. You could do something like this:

local tble =  {{"Level", 1}, {"Quantity", 2},{"Colour", 3}}

table.sort(tble, function(a, b)
	return a[2] > b[2]
end)

print(tble[1][2], tble[2][2], tble[3][2]) -- 3, 2, 1
2 Likes

I am intentionally doubling up on my brackets

So “Lev1” will be a table containing:

{colour = "Gold", quantity = 1, level = 1};
{colour = "White", quantity = 1, level = 1};
{colour = "Yellow", quantity = 1, level = 1};

And “Lev2” will look like:

{colour = "Black", quantity = 1, level = 2};
{colour = "White", quantity = 1, level = 2};

And I then sort each of these on their own so that each table is sorted according to it’s rarity (not shown), quantity, and finally alphabetical order of colour.

I’ve done this intentionally, and I’m not sure why this seemingly inserts random “{};” values into my Lev1/Lev2 tables?

Or am I missing the point :confused:

And @posatta yeah I’m aware

Edit: I will try your method in a bit polill, thanks!

Edit2: Wait actually, @polill00, I am sorting tables like that?

table.insert(Lev1,{colour = key, quantity = value.lev1, level = 1})

Or can I not have variables stored in a table

1 Like

This works:

local Lev1 = {

{colour = "Gold", quantity = 1, level = 1},

{colour = "White", quantity = 1, level = 2},

{colour = "Yellow", quantity = 1, level = 3}}

table.sort(Lev1, function(a, b)

return(a.level > b.level)

end)

print(Lev1[1].colour, Lev1[2].colour, Lev1[3].colour) -- Yellow, White, Gold

Can you show us the “Objects” fed into this function?

The key/values that are fed in are shown above ^^. There is then also a few tables which allows you to get the rarity of the item according to it’s colour which I haven’t bothered adding.

So the objects being fed into the table are
{data["Black"], data["White"]}

I’ve ran into this issue before, answered in this stackoverflow (I didn’t link it because I don’t think stackoverflow links are allowed)

Basically your compare function has to be consistent in comparing values. In the case that two objects are equivalent, or should be sorted indifferently, you should return false.

If sort(a, b) returns true, and sort(b, a) also returns true then lua will freak out for some reason and throw you nil values. Something with the sorting algorithm is leaking out here. Make sure in cases like this sort(a, b) and sort(b, a) both return false, or one or the other returns true, but not both.

3 Likes

So if I’m trying to sort my data against three criteria, will this cause errors?

Let’s say that my data “Black” is common but there’s a quantity of 2, and that my data “White” is rare, but there’s a quantity of 1.

I want all my highest ranks first, then if the ranks are the same all the highest quantities, then if the quantities are the same by alphabetical order…

When my table compares a->b (white->black) it will return true at the first check of “Is a’s rank higher than b’s rank”. When it then comes back round to a is black and b is white, it will realise that a’s rank is higher than b’s rank, but it will then check for quantity and will say “B has more quantity than white”, and so will return true there.

Do you know of a better way I could compare my table against all of these criteria consistently?

EDIT: No, that does make sense. Thanks for your help all, I’ve fixed it with these nested ifs:

		if rank1 == 0 or rank2 == 0 then print("ERROR NO RANK FOUND FOR "..clr1) print("or "..clr2) end
		if rank1>rank2 then print(clr1.." is a higher rank than "..clr2) return true end
		if rank1 == rank2 and a.quantity>b.quantity then return true end
		if rank1 == rank2 and a.quantity == b.quantity then return a.colour>b.colour end
		return false