Querying hash with "identical" keys

This is a strange one… So strange that my code needs to be tweaked for SU2017 on Windows only… But let me provide the following snippet first:

p1 = Geom::Point3d.new(1.0, 2.0, 3.0)
p2 = Geom::Point3d.new(4.0, 5.0, 6.0)

h = Hash.new
h[[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]] = 7.0

k1 = [*p1, *p2]
k2 = [p1.x, p1.y, p1.z, p2.x, p2.y, p2.z]

puts k1 == k2 # Prints true
puts h[k1] # Prints 7.0, as expected
puts h[k2] # Prints nothing :(

What is going on here? In my actual code I’m setting up a similar hash and then I query it later on. I was using the asterisks approach to unpack the values and it worked just fine in SU2022 on macOS, but when testing on Windows with SU2017 I needed the opposite approach… Then I’ve tested SU2017-macOS and SU2023-Windows and the asterisks worked there too.

I am completely baffled.

Never mind, I’ve fixed the problem in my extension code (turns out that I needed to explicitly call .to_f on each .x, .y and .z when adding items to my hash). I’m still puzzled about the snipped above, though.

EDIT. Let me add another k3 key:

k3 = [p1.x.to_f, p1.y.to_f, p1.z.to_f, p2.x.to_f, p2.y.to_f, p2.z.to_f]
puts h[k3] # Prints 7.0 again!

Be aware that the API docs are incorrect, in that the .x, .y and .z methods of the Geom::Point3d class return Length not Float nor generic Numeric.

This means that comparisons will use the API Length#== method to find matches within SketchUp’s tolerance of 0.001 inch.

This may not be true for Float which is subject to floating point errors and could cause you grief.

1 Like

Yeah, it was a Float vs Length issue… This can be seen by calling .class on the key elements, e.g.

k1[0].class # Returns Float
k2[0].class # Returns Length

It’s funny that k1.hash == k2.hash is true, so I thought the dictionary lookup would succeed anyway. Seems like the Ruby hash takes into account the class attribute as well.

The Ruby docs for Hash#== say …

Equality—Two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (according to Object#==) the corresponding elements in the other hash.

So, you have both key match comparisons and value comparisons. The value comparisons will use the overridden #== method for their class, but if not overridden then Ruby will search up the inheritance (ancestry) chain for the first #== method it finds to use for the comparison.

For Length it’s #== method has been overridden to test within SketchUp’s tolerance.

For Float it has also an overridden #== method that can compare objects of different Numeric subclasses. Make sure you read this about the Float comparisons and how it’s #== method can mislead you.

I would strongly suggest you should use arrays and hash values converted to Length objects.

1 Like