4

请参阅下面的示例

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]]预期的结果。)这是一个错误吗?

4

1 回答 1

7

是的,这是一个错误,或者至少我称之为错误。有人会称之为“一个意外泄露给外界的实现细节”,但这只是花哨的城市男孩对bug的谈话。

问题有两个主要原因:

  1. 您在 Set 不知情的情况下修改 Set 的元素。
  2. 标准的 Ruby Set实现为 Hash。

结果是您在 Hash 不知道的情况下修改了内部 Hash 的键,这使可怜的 Hash 变得不知道它有什么键。Hash 类有一个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哈希使用key 的值来跟踪事物k。如果你有一个数组作为键并且你改变了数组,你也可以改变数组的hash值;结果是哈希仍然将该数组作为键,但哈希将无法找到该数组作为键,因为它将在桶中查找新hash值,但该数组将在桶中旧hash值。但是,如果你rehash是 Hash,它会突然再次找到它的所有密钥,并且衰老消失了。非数组键也会出现类似的问题:您只需更改键,使其hash值更改并且包含该键的哈希会变得混乱并四处游荡,直到您丢失为止rehash

Set 类在内部使用 Hash 来存储其成员,并且成员用作散列的键。因此,如果您更改成员,则 Set 将变得混乱。如果 Set 有一个rehash方法,那么您可以通过将 Set 倒置头部来解决问题,以使其具有rehash某种意义;唉,Set中没有这样的方法。但是,您可以修改自己的猴子补丁:

class Set
  def rehash
    @hash.rehash
  end
end

然后您可以更改键,调用rehashSet,您的delete(以及其他各种方法,例如member?)将正常工作。

于 2012-04-28T07:34:36.453 回答