Table.find() not functioning the way I expect it to

So, I’m making a game module for a project I’m working on, and for some reason table.find(table, value) doesn’t work to return the index of the value when the index is not 1 (there are 24 total indexes).

The problematic function is this:

GameModule.RemoveTribute = function(self: typeof(GameModule), player: Player)
	for i, v in self.TributesTable do
		print(i, v.Name)
	end
	local tributeIndex = table.find(self.TributesTable, player)
	if tributeIndex then
		self.TributesTable[tributeIndex] = nil
		return tributeIndex
	end
end

and the function that initializes the table is this:

GameModule.new = function()
	local self = setmetatable({}, GameModule)
	
	self.TributesTable = table.create(24)
	
	self.InSession = false
	
	return self
end

Does table.find() bug out when there are 23 nil indexes and you are attemping to table.find() on an index that is > 1?

I did the for loop to make sure I wasn’t going crazy and that the index doesn’t exist, but it does…

This is what it prints (it’s fired to a local script, but i’m printing to ensure the index is valid before doing anything)
image
yet it works entirely fine with the index as 1;
image

I looked at the Roblox Documentation to see if there was any information on the behavior for table.find() in Luau, but there’s… nothing? It doesn’t make sense. Does anyone have any insight on this and has a suggestion for what I should try?

I think the 23 nil indexes you mentioned might be your culprit.
Luau is treating the table as a dictionary, even though it has number indexes.

The way Luau determines an array is consecutive, ascending indexes starting from one. As soon as that chain is broken, the rest of the table is treated as a dictionary.

You can test with this behaviour:

local tbl = {
    [1] = 3,
    [2] = 5,
    [4] = 7,
    [8] = 9
}

print(table.find(tbl, 3)) --> 1
print(table.find(tbl, 5)) --> 2
print(table.find(tbl, 7)) --> nil
print(table.find(tbl, 9)) --> nil

Hence, 23 indexes will break that chain provided there are not valid consecutive indexes starting from 0. The solution is to make sure table elements are shifted to close gaps when not in use, for example using table.remove.

3 Likes

Hm, okay. That’s strange. I could do a for loop, I just wish that it would have worked in a much simpler, prettier manner.
Thank you! I’ll keep this open in case anyone has some form of workaround I could possibly work with (and if not, then oh well. I can live with it)

2 Likes

You can try creating an array filled with zeros instead or any other value beside nil in GameModule.new

table.create(24, 0))

edit: as @12345koip stated, it is indeed better to use false instead of zero

2 Likes

if you want a workaround, you could just give it a separate method to implement iteration logic. table.find will still work on the array part of the table if you wanted it to anyway.

function myTable.getkey(value: any)
    for k, v in myTable do
        if (v == value) then return k end
    end
end

(if the table is large, you could use a different searching algorithm)

2 Likes

i guess something like

find = function(table, query)
	for key, value in table do
		if value == query then
			return key
		end
	end
	return nil
end

local tbl = {
	[1] = 2,
	[2] = 4,
	[4] = 8,
	[8] = 16
}

print(find(tbl, 2)) -- 1
print(find(tbl, 4)) -- 2
print(find(tbl, 8)) -- 4
print(find(tbl, 16)) -- 8

could work then

edit: i just realized you already posted a better version of this but whatever

2 Likes

That works. I think that’d probably be the most memory efficient that is feasible with table.find().

If I feel it uses too much memory (which, luckily for me, this is server-sided), I can always do the for loop iteration, it just feels less clean than being able to do table.find().

1 Like

Is there any particular reason you can’t just leave the keys removed from the table and have other elements shifted down? remember there is an overloaded version of table.insert which can insert at a specific index, and would likely be more efficient.

2 Likes

it is specifically necessary, yes, because it is intended to be a queue where players can be randomly inserted from any index between 1 and 24. thus, if there is an index at 7, it is not necessarily true that indexes 1-6 are also filled.

and the index refers to a position (it’s less of a position in the queue and more an identifier i need). for example, if someone has a Tribute Number of 23, they must be in District 12, tribute 1. So, if I were to attempt to shift it to index 7, it would be District 4, tribute 1 (which was not queried)

1 Like

in that case, @idealcrime’s solution would probably be best. However, you might want to use false as the value, because it’s a falsy value and won’t allow the key to get gc’d. Then, you can literally just write if not tbl[key] then instead of if tbl[key] ~= 0 then.

2 Likes

Do you have any clue how many bytes each use? I’m aware false and true are technically stored as 1 and 0, but also numbers are stored as doubles, so they take up 8 bytes each (and that therefore uses something like 192 bytes to store 24 0 values.

If not, I’ll just happily look it up to see which one would be best memory-wise (if either)

true and false actually take up 1 byte each… sorry no need to correct that lol

0 is still a number, so is still a double. Keep in mind Luau functions tend to be very lightweight, so if you’re concerned about memory you could use my algorithmic approach. However, then again, this will be tiny micro-optimisations, so it won’t impact performance that much which you use.

2 Likes

alr thanks; at least they only take up 1 byte rather than 8. every byte matters to me!

1 Like

This is the solution I believe I will roll with, except 0 will be false