哈希使用数组作为ruby中的键

时间:2012-08-29 11:39:23

标签: ruby hash

我有一个使用数组作为键的哈希。当我更改数组时,哈希不再能获得相应的键和值:

1.9.3p194 :016 > a = [1, 2]
 => [1, 2] 
1.9.3p194 :017 > b = { a => 1 }
 => {[1, 2]=>1} 
1.9.3p194 :018 > b[a]
 => 1 
1.9.3p194 :019 > a.delete_at(1)
 => 2 
1.9.3p194 :020 > a
 => [1] 
1.9.3p194 :021 > b
 => {[1]=>1} 
1.9.3p194 :022 > b[a]
 => nil 
1.9.3p194 :023 > b.keys.include? a
 => true 

我做错了什么?

更新: 好。使用a.clone绝对是解决这个问题的一种方法。 如果我想更改“a”但仍然使用“a”来检索相应的值(因为“a”仍然是其中一个键),该怎么办?

5 个答案:

答案 0 :(得分:15)

#rehash方法将重新计算哈希值,因此在键更改后执行:

b.rehash

答案 1 :(得分:5)

TL; DR:考虑Hash#compare_by_indentity

您需要决定是否希望哈希按数组或数组身份工作。

默认值为数组.hash.eql?,这就是为什么更改值会让ruby感到困惑。考虑一下您的示例的变体:

pry(main)> a = [1, 2]
pry(main)> a1 = [1]
pry(main)> a.hash
=> 4266217476190334055
pry(main)> a1.hash
=> -2618378812721208248
pry(main)> h = {a => '12', a1 => '1'}
=> {[1, 2]=>"12", [1]=>"1"}
pry(main)> h[a]
=> "12"
pry(main)> a.delete_at(1)
pry(main)> a
=> [1]
pry(main)> a == a1
=> true
pry(main)> a.hash
=> -2618378812721208248
pry(main)> h[a]
=> "1"

看看那里发生了什么? 正如您所发现的那样,它无法与a键匹配,因为它存储它的.hash值已过时[BTW,您甚至无法依赖它!突变可能导致相同的散列(稀有)或不同的散列落在同一个桶中(不是那么罕见)。]

但是,不是通过返回nil而失败,而是在a2键上匹配 请参阅h[a]并不关心a vs a2(叛徒!)的身份。它将您提供的当前 - [1]a2的{​​em>值 [1]进行了比较并找到匹配项。

这就是使用.rehash只是创可贴的原因。它将重新计算所有键的.hash值并将它们移动到正确的桶中,但它容易出错,并且可能也会造成麻烦:

pry(main)> h.rehash
=> {[1]=>"1"}
pry(main)> h
=> {[1]=>"1"}

哦,哦。这两个条目合并为一个,因为它们现在具有相同的值(并且难以预测哪些胜利)。

解决方案

一种理智的方法是接受按值查找,这要求价值永远不会改变。 .freeze你的钥匙。或者在构建哈希时使用.clone / .dup,并随意改变原始数组 - 但接受h[a]将针对保留的值查找a的当前值从建立时间开始。

另一个,你似乎想要的,是决定你关心身份 - a的查找应该找到a无论当前的价值如何,如果有很多密钥,那就不重要了或现在具有相同的价值 怎么样?

  • Object按身份进行哈希。 (数组不是因为.==按值的类型也倾向于覆盖.hash.eql?是值。)因此,一个选项是:不要使用数组作为键,使用一些自定义类(可能在其中包含一个数组)。

  • 但是,如果你希望它直接像数组的哈希一样呢?你可以将哈希或数组子类化,但是要使一切工作始终如一,需要付出很多努力。幸运的是,Ruby有一种内置方式:h.compare_by_identity切换哈希按身份工作(无法撤消,AFAICT)。如果在插入任何内容之前执行此操作,您甚至可以使用具有相同值的不同键,而不会产生混淆:

    [39] pry(main)> x = [1]
    => [1]
    [40] pry(main)> y = [1]
    => [1]
    [41] pry(main)> h = Hash.new.compare_by_identity
    => {}
    [42] pry(main)> h[x] = 'x'
    => "x"
    [44] pry(main)> h[y] = 'y'
    => "y"
    [45] pry(main)> h
    => {[1]=>"x", [1]=>"y"}
    [46] pry(main)> x.push(7)
    => [1, 7]
    [47] pry(main)> y.push(7)
    => [1, 7]
    [48] pry(main)> h
    => {[1, 7]=>"x", [1, 7]=>"y"}
    [49] pry(main)> h[x]
    => "x"
    [50] pry(main)> h[y]
    => "y"
    

    请注意这样的哈希are counter-intuitive if you try to put there e.g. strings,因为我们真的习惯于按值进行字符串散列。

答案 2 :(得分:2)

哈希使用其关键对象的哈希码(a.hash)对它们进行分组。散列码通常取决于对象的状态;在这种情况下,当从数组中删除元素时,a的哈希码会发生变化。由于密钥已经插入到哈希中,因此a将在其原始哈希码下归档。

这意味着您无法在a中检索b的值,即使打印哈希值时它看起来也不错。

答案 3 :(得分:1)

您应该使用a.clone作为密钥

irb --> a = [1, 2]
==> [1, 2]

irb --> b = { a.clone => 1 }
==> {[1, 2]=>1}

irb --> b[a]
==> 1

irb --> a.delete_at(1)
==> 2

irb --> a
==> [1]

irb --> b
==> {[1, 2]=>1} # STILL UNCHANGED

irb --> b[a]
==> nil # Trivial, since a has changed

irb --> b.keys.include? a
==> false # Trivial, since a has changed

使用a.clone即使我们稍后更改a,也会确保密钥不变。

答案 4 :(得分:1)

正如您已经说过的,问题是哈希键是您稍后修改的完全相同的对象,这意味着密钥在程序执行期间会发生变化。

要避免这种情况,请复制数组以用作散列键:

a = [1, 2]
b = { a.clone => 1 }

现在,您可以继续使用a并保持哈希密钥不受影响。