Sooji
Sooji

Reputation: 169

Ruby Hash returning nil for value for a key I know exists

I'm going mad trying to resolve a key/value issue on a simple hash inside a hash.

My key is of type "OpenStudio::OptionalString" which comes from an API used in my code:

#---NOTE---: key here is of type OpenStudio::OptionalString

my_hash[key]['heating_plant_system'] = 'Boiler' 
my_value = my_hash[key]['heating_plant_system'] #returning nil

In debug mode I inspected the hash and saw that the first line correctly entered the key/value input, but I am unable to retrieve the value when I run the second line. my_value would return nil. I know it's due to this odd key type, but I'm not at liberty to change it.

Am I making a dumb mistake when trying to access this value from my hash?


In an attempt to keep things general, I may have sacrificed too much context. This is a complete example:

require 'openstudio'

model = OpenStudio::Model::Model.new
my_zone = OpenStudio::Model::ThermalZone.new(model)

my_zone.setName('Zone 1')

zone_hash = Hash.new { |h, k| h[k] = { } }

zone_hash[my_zone.name]['heating_plant'] = 'Boiler'

puts "my zone's name is #{my_zone.name}" #Output: 'my zone's name is Zone 1'
puts zone_hash.to_s #Output: {#<OpenStudio::OptionalString:0x5fa4980 @__swigtype__="_p_boost__optionalT_std__string_t">=>{"heating_plant"=>"Boiler"}}

if zone_hash[my_zone.name]['heating_plant'].nil?
  puts 'Im unable to access this hash, help!' #<--- this is executed
else
  puts "I am able to access #{zone_hash[my_zone.name]['heating_plant']}"
end

Since I could not (easily) undo how this zone_hash worked in my actual code base by changing the key to something other than OpenStudio::OptionalString, I used this loop as a work around. It's not pretty, but it got the job done for the small checks I needed to do:

zones_hash.each {|k,v|
   if zone.name.to_s == k.to_s
     v.each {|k1,v1|
       if k1 == 'heating_plant'
         heating_plant = v1.to_s
       end
     }
   end
 }

Upvotes: 1

Views: 4395

Answers (2)

the Tin Man
the Tin Man

Reputation: 160549

A hash key can be any object type as long as the objects are unique. The definition for uniqueness or equality is found in "Hash Keys".

Meditate on this:

v1 = %w(a b)
v2 = %w(c d)
hash = {v1 => 1, v2 => 2}
hash # => {["a", "b"]=>1, ["c", "d"]=>2}
hash[v1] # => 1
hash[%w(a b)] # => 1

As long as the key is unique you can use it:

class Foo
end

foo1 = Foo.new
foo2 = Foo.new

foo1.hash # => 1202274030892197226
foo2.hash # => 2774925608615277787

hash = {foo1 => 1, foo2 => 2}
hash[foo1] # => 1

Or even:

class Foo
  def initialize
    @init_time = Time.now
  end

  def init_time
    @init_time
  end
end

foo1 = Foo.new
foo2 = Foo.new

foo1.init_time.to_f # => 1484874655.324574
foo2.init_time.to_f # => 1484874655.324577

hash = {foo1.init_time => 1, foo2.init_time => 2}
hash[foo1.init_time] # => 1

Not quite true, Ruby uses hash and eql? for hash equality, it just so happens that the default implementation relies on object_id

You're right, it's been a long time since I cared exactly why they were unique. From the docs:

Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other.

Upvotes: -1

akuhn
akuhn

Reputation: 27793

Ruby uses hash and eql? to check the equality of hash keys.

Looks like OpenStudio::OptionalString might not correctly implement those. If that is the case your best solution is using another key.

Ruby makes the following assumptions for hash keys—if two objects are to be considered the same key they must return the same hash value, however having the same hash value does not mean they are the same key. The method eql? is used internally to resolve these cases.

You could also fix the hash and eql? methods on the OpenStudio::OptionalString class, but maybe that library relies on the "broken" behavior internally. That is why I would recommend just using another hash key like for example a string representation of those objects.

Upvotes: 4

Related Questions