参见下面的示例
require "set"
s = [[1, 2], [3, 4]].to_set # s = {[1, 2], [3, 4]}
m = s.max_by {|a| a[0]} # m = [3, 4]
m[0] = 9 # m = [9, 4], s = {[1, 2], [9, 4]}
s.delete(m) # s = {[1, 2], [9, 4]} ?????
这与数组的行为不同。 (如果我们移除.to_set
,我们会得到预期的s = [[1, 2]]
。)这是一个错误吗?
答案 0 :(得分:5)
是的,这是一个错误,或者至少我称之为错误。有些人会称这个"一个实施细节不小心泄漏到外面的世界"但这只是花哨的裤子城市男孩谈论 bug 。
问题有两个主要原因:
结果就是你在没有Hash知道的情况下修改内部Hash的密钥,这使得糟糕的Hash混淆不知道它不再具有什么密钥。 Hash类有一个rehash
method:
rehash→hsh
根据每个键的当前哈希值重建哈希值。如果键对象的值自插入后已更改,则此方法将重新索引 hsh 。
a = [ "a", "b" ] c = [ "c", "d" ] h = { a => 100, c => 300 } h[a] #=> 100 a[0] = "z" h[a] #=> nil h.rehash #=> {["z", "b"]=>100, ["c", "d"]=>300} h[a] #=> 100
请注意rehash
文档中包含的示例中的有趣行为。哈希使用密钥k.hash
的{{1}}值来跟踪事物。如果您将数组作为键并更改数组,则还可以更改数组的k
值;结果是Hash仍然将该数组作为键,但Hash不能将该数组作为键发现,因为它将在桶中查找新的hash
值但数组将在旧的hash
值的桶中。但是,如果你{哈希} hash
,它会突然能够再次找到它的所有键,衰老就会消失。非数组键也会出现类似的问题:您只需更改键,其rehash
值会发生变化,包含该键的Hash会混淆并在您丢失之前四处游荡,直到hash
为止它
Set class在内部使用Hash来存储其成员,并且成员用作哈希的键。因此,如果您更改成员,该集将会混淆。如果Set有一个rehash
方法,那么你可以通过用rehash
将头部设置为头部来解决问题,从而使其感觉不适;唉,Set中没有这样的方法。但是,你可以自己修补:
rehash
然后,您可以更改密钥,在设置上调用class Set
def rehash
@hash.rehash
end
end
,rehash
(以及delete
等各种其他方法)也能正常运行。