【问题标题】:Ruby Hash returning nil for value for a key I know existsRuby Hash 为我知道存在的键返回 nil 的值
【发布时间】:2024-01-22 11:12:01
【问题描述】:

试图解决散列中的简单散列上的键/值问题,我快疯了。

我的密钥属于“OpenStudio::OptionalString”类型,它来自我的代码中使用的 API:

#---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

在调试模式下,我检查了哈希,发现第一行正确输入了键/值输入,但在运行第二行时无法检索值。 my_value 将返回零。我知道这是由于这种奇怪的键类型,但我无权更改它。

当我试图从我的哈希中访问这个值时,我犯了一个愚蠢的错误吗?


为了让事情保持一般性,我可能牺牲了太多的上下文。这是一个完整的例子:

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

由于我无法(轻松地)通过将密钥更改为 OpenStudio::OptionalString 以外的其他内容来撤消 zone_hash 在实际代码库中的工作方式,因此我使用此循环作为解决方法。它不漂亮,但它完成了我需要做的小检查:

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
 }

【问题讨论】:

  • 你能显示my_hash[key].keys的输出吗?
  • 请阅读“minimal reproducible example”。我们需要最少的输入和预期的输出,以便我们可以测试您的代码和我们所做的任何调整。
  • 哈希键可以是任何对象类型,只要对象是唯一的。如果它们不是唯一的,它们将踩在以前的匹配条目上。
  • key是如何初始化的?你从key.to_s 得到什么?你可以使用key.to_s 作为键吗?
  • 请不要在文本中使用“编辑”或“更新”类型的标签。而是将附加信息添加到文本中,就好像它从一开始就在那里一样。我们可以看到发生了什么变化,以及何时需要。

标签: ruby hash


【解决方案1】:

Ruby 使用hasheql? 来检查哈希键的相等性。

看起来OpenStudio::OptionalString 可能无法正确实现这些。如果是这种情况,您最好的解决方案是使用另一个密钥。

Ruby 对哈希键做了以下假设——如果两个对象被认为是相同的键,它们必须返回相同的 hash 值,但是具有相同的哈希值并不意味着它们是相同的键。内部使用方法eql? 来解决这些情况。

您还可以修复 OpenStudio::OptionalString 类上的 hasheql? 方法,但该库可能依赖于内部的“损坏”行为。这就是为什么我建议只使用另一个哈希键,例如这些对象的字符串表示。

【讨论】:

    【解决方案2】:

    哈希键可以是任何对象类型,只要对象是唯一的。唯一性或相等性的定义见“Hash Keys”。

    思考这个:

    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
    

    只要密钥是唯一的,您就可以使用它:

    class Foo
    end
    
    foo1 = Foo.new
    foo2 = Foo.new
    
    foo1.hash # => 1202274030892197226
    foo2.hash # => 2774925608615277787
    
    hash = {foo1 => 1, foo2 => 2}
    hash[foo1] # => 1
    

    甚至:

    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
    

    不完全正确,Ruby 使用 hash 和 eql?对于哈希相等,恰好默认实现依赖于 object_id

    你说得对,我已经很久没有真正关心它们为何如此独特了。来自the docs

    当两个对象的hash 值相同并且两个对象彼此为eql? 时,它们引用相同的哈希键。

    【讨论】:

    • 不完全正确,Ruby 使用 hasheql? 来实现哈希相等,碰巧默认实现依赖于 object_id
    • @akuhn 在某些情况下,实现不依赖于object_id。考虑一下。 arr = [1]; hash = {}; hash[arr] = 1; arr &lt;&lt; 2; hash[arr] #=&gt; nil。然而 arr 始终是同一个对象
    • Array 不使用默认实现。字符串也不行。这正是我要说的,哈希键不使用“唯一”性,而是使用hasheql? 定义的身份。