BrickColor as table key

Is this expected behavior?

local a = {}
a[BrickColor.new("Cyan")] = true
print(a[BrickColor.new("Cyan")])

nil

print(BrickColor.new("Cyan") == BrickColor.new("Cyan"))

true

Edit:
Just realized this is for all userdatas… its probably intentional but still confusing.

Edit 2:
Might be nice if Roblox had better hashing for their userdatas instead of just use the memory address (I think thats what they do since that’s why they are different?)

You can’t assign dictionaries like that, here’s a workable version of your code where BrickColor.new(“Cyan”) is compared as a value instead of a key:

local a = {cyan = BrickColor.new("Cyan")}
print(a.cyan == BrickColor.new("Cyan"))
-- prints true
1 Like

Yea but then if you want to find the corresponding key you have to search the entire dictionary xd

You could alternatively use the Documentation - Roblox Creator Hub property or Documentation - Roblox Creator Hub but you still aren’t actually storing the BrickColor since its indistinguishable from a raw string/number with the same value :confused:

Edit:

local colorLookups = {}
local function setKey(t,color,value)
	if not colorLookups[color.Number] then
		colorLookups[color.Number] = {}
	end
	
	t[colorLookups[color.Number]] = value
end


local function getKey(t,color)
	if colorLookups[color.Number] then
		return t[colorLookups[color.Number]]
	end
end

local a = {}
setKey(a,BrickColor.new("Cyan"),true)
print(getKey(a,BrickColor.new("Cyan")))

You can do that and it works but it gets messy for other userdatas besides BrickColors xd

1 Like

Why do you even need to do this? You know color3 is supported with parts now right?
If you know the colors name then Colors[Name] works fine

1 Like

It still happens with Color3s:

local a = {}
a[Color3.new(0,0,0)] = true
print(a[Color3.new(0,0,0)])

nil

Also I’m using BrickColors because Teams don’t support Color3s

Also a problem w/ that is that if you have a mixed table w/ a string that matches the color’s name then it will be a problem (highly unlikely)

Ya tbh idk why I made this post xd

When you use a .new function you’re creating new data. You can see this in action:

a = Color3.new()
t[a] = true
print(t[a]) --> true

b = Color3.new()
print(t[b]) --> nil

Try to rearrange the script a tiny bit, I don’t think it’s getting quite what you are saying.
dragonfrosting and Sharksie gave some good advice as to what to do. The code is pretty messy and it would be better to rewrite it then to overcomplicate it even more. Experiment with some things.
As for Edit 2, feature requests could help you. I’d like to see something like that in the future.

tostring is glorious.

local tab = {}
tab[tostring(BrickColor.new("Cyan"))] = BrickColor.new("Cyan")
print(tab[tostring(BrickColor.new("Cyan"))])
-->Cyan

BrickColor.Name seems cleaner to me, if you’re going to do that.

local tab = {}
tab[BrickColor.new(“Cyan”).Name] = BrickColor.new(“Cyan”)
print(tab[BrickColor.new(“Cyan”).Name])

1 Like

True though I was mostly showing that using tostring you can use nearly anything the way he was wanting, even the color3.new(0,0,0). Tostring is pretty handy when used right.

Might be nice if Roblox had better hashing for their userdatas instead of just use the memory address (I think thats what they do since that’s why they are different?)

There is no “better hashing” to have. Lua doesn’t let you define a hash function. Either two things are the same object, and they act as the same table key, or they aren’t, and they don’t… with the sole exception of strings, since they are the only non-trivial value-type in Lua and it’s actually impossible to tell whether they are the same object in memory or not from the Lua side. The only way Roblox could do this is to significantly customize the Lua implementation… which is a pretty drastic measure that they would not want to take unless it had serious benefits.

Just realized this is for all userdatas… its probably intentional but still confusing.

It’s not true for Instances. There is a global Ref registry so that whenever you get a reference to an instance, it’s the exact same reference object, hence:

a[workspace.Camera] = 6
print(a[workspace.Camera]) --> 6

So, why doesn’t Roblox do the same thing for BrickColors that they do for Instances?

This might be possible for BrickColors specifically, since they are palletted, but it would be impractical to cache every created object for Vector3s, UDim2s, etc since they have have infinitely many possible configurations. And for consistency, having BrickColors act differently than every other userdata type would be very surprising and confusing.

Not only that, but it’s a very bad idea conceptually to use these things as table keys, so there’s no reason to encourage it. Would you ever use Torso.Position.x as a table key? Of course not, it’s basically useless to do so. And the same is true for almost every use of a Color3 / Vector3 etc as a table key.

TL;DR: This is perfectly reasonable behavior.

2 Likes

If you pass the brickcolor number or something into the hash function (which isn’t directly modifying the hash function I think) instead of passing the memory address or whatever is done + a type(-of) identifier like is probably done with strings and maybe bools then you have hashing for userdatas too.

Instances have the same behavior as the rest of the userdatas:

local a = {}
a[Instance.new("Part")] = true
print(a[Instance.new("Part")])

it outputs nil just like if you were to do:

local a = {}
a[BrickColor.new("Cyan")] = true
print(a[BrickColor.new("Cyan")])

but if you do:

local a = {}
local color = BrickColor.new("Cyan")
a[color] = true
print(a[color]]

it would output true just like in your example.

Roblox already caches every single userdata created until it is garbagecollected. I assume you mean create an array with all of the possible configurations though? But thats one of the reason we have hash tables: to make the array size closer to the amount of elements.

Would be nice to have this for every userdata :stuck_out_tongue_winking_eye:

print(type(Torso.Position.x) == "number")

Its a number though

Instances have the same behavior as the rest of the userdatas:

Ehh… I guess they’re just not really comparable if you look at it that way. Instances are supposed to act like reference-types, while all the other userdatas are supposed to act like value-types so you can’t really compare their behavior like that.

No, it definitely doesn’t. Otherwise you would not be seeing the behavior that you are. They obviously exist until they are garbage collected, but they aren’t cached, there’s a huge difference between those two things. Cached implies that they can actually be looked up and re-used based on the data they contain.

Would be nice to have this for every userdata

I don’t know about that. I would be surprised if you could give me even a single convincing use case for it.

Its a number though

Yeah, an arbitrary number, that you won’t necessarily be able to construct ever again other than by accessing the very same number through a variable / table you put it into. Which is my point, much the same thing applies to the userdata types, using them as keys is not useful for the same reasons.

2 Likes

numbers, strings, and bools are values
userdatas should have the value properties and not act as memory addresses when used in hash tables.

oops never knew that xd

what if you want to have a region3 table with frequently looked up region3s and the parts that belong inside of them for O(1) search time, either you do O(n) search through all of the different items in the dictionary or you create a 6 dimensional number dictionary :confused:

what if you want to lookup parts by their cframes (and orientation matters)?
you probably also have to also do a minimum of a 6 dimensional array (position and orientation) - correct me if im wrong here pls

???

local a = {}
a[32.5445] = true
print(a[32.5445])

Outputs true, hashing for numbers works as expected/how I want userdatas to work?

This is exactly what I’m talking about—this is not a legitimate use case. Even if you want to do something like this, CFrames are not the keys you want. Either you know exactly what CFrames actually have a part at that exact CFrame ahead of time, in which case you should label those CFrames with actual useful labels, and use those labels as keys, or you don’t know what CFrames contain parts ahead of time, and there’s basically a 0% chance that a given CFrame you use as a key will actually have anything at that exact location.

In either case using the CFrames as keys is very wrong.

I am also :confused: as to what you’re trying to propose here, I don’t really understand what your “parts that belong inside of them” is describing… what are you actually trying to do here?

2 Likes

Parts that belong inside of them are like the table of parts calling workspace.FindPartsInRegion3 would return. If you have a huge map thats like 50k parts (or bigger), it would be pretty inefficient to constantly call the function if you are going to be reusing some of the parts in the regions.

So, where do you need a hash-table at all in this? What are you looking up by? The region3?

Again, that doesn’t make any sense. You could just “label” each region something like “house” / “base” / “bridge” and then look up by the label of the region in the table, there’s no reason to use a Region3 as the key. And if you have an arbitrary number of regions… then the chances of actually hitting the exact same region twice are basically zero, so your table won’t accomplish anything.

You’re trying to put your data inside of your table keys, when it should be inside of your table values. They keys need to be something you can actually look up based on.

2 Likes

I don’t know if this was already said but it may be because the __eq metamethod of BrickColor objects compare the two BrickColors’ codes/names. The __eq metamethod doesn’t get fired when using it to index a table so it’s definitely expected behavior. Creating two of the “same” BrickColor must actually create two separate objects then.

I didn’t thoroughly research this btw this is just my best guess

2 Likes