我有一个使用数组作为键的哈希.当我更改数组时,哈希不再能获得相应的键和值:
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
Run Code Online (Sandbox Code Playgroud)
我究竟做错了什么?
更新:好的.使用a.clone绝对是解决这个问题的一种方法.如果我想更改"a"但仍然使用"a"来检索相应的值(因为"a"仍然是其中一个键),该怎么办?
ste*_*lag 16
该#rehash方法将重新计算哈希值,所以以后的主要变化做:
b.rehash
Run Code Online (Sandbox Code Playgroud)
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"
Run Code Online (Sandbox Code Playgroud)
看看那里发生了什么?正如您所发现的那样,它无法与a密钥匹配,因为.hash它存储它的值已经过时了[顺便说一下,你甚至不能依赖它!突变可能会导致同一个哈希(稀有)或不同的哈希落在同一个桶中(不是那么罕见).
但不是通过返回失败nil,而是在a1键上匹配.
见,h[a]根本不会在乎身份的aVS a1(叛徒!).它比目前的价值你提供- [1]与价值的a1是[1],发现一个匹配.
这就是为什么使用.rehash只是创可贴的原因.它将重新计算.hash所有键的值并将它们移动到正确的桶中,但它容易出错,并且还可能导致麻烦:
pry(main)> h.rehash
=> {[1]=>"1"}
pry(main)> h
=> {[1]=>"1"}
Run Code Online (Sandbox Code Playgroud)
哦,哦.这两个条目合并为一个,因为它们现在具有相同的值(并且很难预测哪些胜利).
一种理智的方法是接受按值查找,这要求价值永远不会改变. .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"
Run Code Online (Sandbox Code Playgroud)
请注意,如果你试图将字符串放在那里,这样的哈希是违反直觉的,因为我们真的习惯于按值进行字符串散列.
你应该使用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
Run Code Online (Sandbox Code Playgroud)
使用a.clone将确保即使我们a稍后更改密钥也不会更改。