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
值已过时[顺便说一句,您甚至不能依赖于它!突变可能导致相同的哈希(罕见)或不同的哈希落在同一个桶中(不太罕见)。]
但是,它没有返回
nil
而是匹配了
a1
键。
看到了吗,
h[a]
根本不关心
a
与
a1
(叛徒!)的
身份。它将您提供的当前
值——
[1]
与
a1
的
值——
[1]
进行比较并找到匹配项。
这就是为什么
使用.rehash
只是临时措施。它会重新计算所有键的
.hash
值并将它们移动到正确的桶中,但它容易出错,并可能引起麻烦:
pry(main)> h.rehash
=> {[1]=>"1"}
pry(main)> h
=> {[1]=>"1"}
哦哦。由于它们现在具有相同的值(并且很难预测哪个会获胜),因此这两个条目合并为一个。
解决方案
一个明智的方法是采用按值查找的方式,这需要该值永远不会更改。请 .freeze
您的键。或者在构建哈希表时使用 .clone
/.dup
,并随意更改原始数组 —— 但接受 h[a]
将根据从构建时保存的值查找当前的 a
值。
另一种方法是决定您关心标识 —— 按 a
查找应该找到 a
,无论其当前值如何,并且许多键具有或现在具有相同的值都不重要。
怎么做呢?
Object
hashes by identity. (Arrays don't because types that .==
by value tend to also override .hash
and .eql?
to be by value.) So one option is: don't use arrays as keys, use some custom class (which may hold an array inside).
But what if you want it to behave directly like a hash of arrays? You could subclass Hash, or Array but it's a lot of work to make everything work consistently. Luckily, Ruby has a builtin way: h.compare_by_identity
switches a hash to work by identity (with no way to undo, AFAICT). If you do this before you insert anything, you can even have distinct keys with equal values, with no confusion:
[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"
Beware that such hashes are counter-intuitive if you try to put there e.g. strings, because we're really used to strings hashing by value.