As a Roblox developer, it is currently too hard to count the number of items in a dictionary or a set. This is because the # operator only works on arrays. As of now, the best way to do this is to iterate over the dictionary and count the length that way:
local length = 0
for _, _ in myDictionary do
length += 1
end
print(`myDictionary is {length} items long`)
This is a common operation that I find myself performing very regularly. Currently I’m making a UI where a label’s text changes to a different format depending on the length of the user’s inventory, which is stored as a set. Another previous use is checking to see if the user’s inventory is full.
There should be an extremely simple built-in way to get the length of a dictionary, probably a table.numkeys(myDictionary) or something.
If Roblox is able to address this issue, it would improve my development experience because I wouldn’t have to write a function to count the number of items in a dictionary.
You can always use a function, and you actually can use the # operator on dictionaries.
-- len function
local function len(tbl: {[any]: any}): number
local count: number = 0
for _, _ in tbl do
count += 1
end
return count
end
local myDictionary = {
key1 = true,
key2 = 1
}
print(`myDictionary is {len(myDictionary)} items long`)
-- len mt
local len_mt = {__len = function(self)
local count: number = 0
for _, _ in self do
count += 1
end
return count
end,}
local myDictionary = setmetatable({
key1 = true,
key2 = 1,
key3 = "string"
}, len_mt)
print(`myDictionary is {#myDictionary} items long`)
Instead, we should be able to get all the keys and values as a list, then we can get the length of that normally. This would help in more ways than one!
I personally say it’s a little too simple to ask for and you may as well make a function to do this. Maybe even put it in the global table so all scripts can access it (function _G.nonNillKeyCount(dict)) (but you need to make sure this script has ran before calling the function)
Ofc, there is no harm in it and it’d be handy.
Maybe im bias because almost no language lets you do this, so it feels wohrkspapwjrdioqpqlw to me
Arrays and strings store their length as a constant value so the # can be done in O(1) time. Hashmaps (dictionaries), don’t have that since they are associative/linked lists, so counting can only be done in O(n) time.
Even if a library function was added, the extra trapping and error-checking wouldn’t make a speedup feasible.
In this case, why don’t you store the length as a separate number and update it when every time you change the set? That’s effectively what the # operator does underneath, anyway.
Because it’s writing additional code to accomplish a basic ability that is included by default with almost every other language I checked. That kind of code is also very prone to desync if some bit of code changes the set but forgets to change the length, unless I make class with custom insert/remove functions that handle both sides, and at this point we’re wrapping back to issue 1 where that is a ridiculous amount of legwork to accomplish an extremely basic task.
That sounds more like a structural problem than a programming language one. You could write an abstraction for this behavior in about 14 lines.
type Inventory = {
Length: number
Labels: {[TextLabel]: string}
}
local function AddLabel(Inventory: Inventory, TextLabel: TextLabel)
Inventory.Length += 1
Inventory.Labels[TextLabel] = TextLabel.Text
end
local function RemoveLabel(Inventory: Inventory, TextLabel: TextLabel)
Inventory.Length -= 1
Inventory.Labels[TextLabel] = nil
end
You can’t expect a language to supply every abstraction possible, and while you could argue getting the length of a map would be useful in theory, in practice without it you end up writing more structured and efficient code.
I don’t, but this one particular abstraction is simple, common, and useful.
The code sample in your reply has a problem - because the Inventory type’s fields are public, someone who doesn’t understand the purpose of the type could modify the Length and Labels values directly, desyncing them and causing what could be a very subtle and hard to detect and fix error. Information like the length of a dictionary should usually be derived directly from the contents of said dictionary, making desync impossible.
It’s possible to create a Table class which uses the __len metamethod like so (ModuleScript in ReplicatedStorage):
local metatable = {
__len = function(table)
local i = 0
for _ in table do i += 1 end
return i
end
}
local Table = {
new = function()
return setmetatable({}, metatable)
end
}
return Table
And a very basic example of how to use it would be something like this:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Table = require(ReplicatedStorage.Table)
local example = Table.new()
print(#example) -- 0
example.key = 123456
print(#example) -- 1
example.key = nil
print(#example) -- 0
But in all honesty a built-in way to get the length of a dictionary like @ChipioIndustries is suggesting has quite the benefits if implemented, namely it removes the need to create and remember to use a custom table class and the calculation would be done on the C++ side
My experience is that every other language with dynamic containers provides a simple way to get the the length: C++, Python, PHP, Perl, Lisp. The closest thing I can think of to a counter-example is C arrays, but they’re rather different in having a fixed length.
There isn’t any reason a table can’t store the length of both the array and hash part. It already handles every single access, so it could track that information.
The ‘problem’ is that Lua/u only supports a single general purpose hybrid container, which has to be sufficient for all use-cases. Compromises such as these, inevitably come with that territory, as any worthwhile implementation of this feature would almost certainly introduce otherwise unnecessary overhead to the bulk of associative operations.