从no-op中删除一个修改过的对象?

时间:2012-04-28 07:05:42

标签: ruby set

参见下面的示例

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]]。)这是一个错误吗?

1 个答案:

答案 0 :(得分:5)

是的,这是一个错误,或者至少我称之为错误。有些人会称这个"一个实施细节不小心泄漏到外面的世界"但这只是花哨的裤子城市男孩谈论 bug

问题有两个主要原因:

  1. 您正在修改Set的元素而不知道它。
  2. 标准Ruby Set实现为哈希。
  3. 结果就是你在没有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等各种其他方法)也能正常运行。