2012-04-28 80 views
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]]这是预期的。)这是一个错误吗?

回答

5

是的,这是一个错误,或者至少我会说它是一个错误。有人会说这是“一个实现细节意外泄漏到外面的世界”,但这只是花哨的裤子城市男孩说话错误

的问题主要有两个原因:

  1. 你修改不设置知道关于它的集合的元素。
  2. 标准红宝石Set被实现为散列。

其结果是,你正在修改内部哈希的密钥而没有哈希知道它,并混淆可怜的哈希成为不知道它有什么关键了。哈希类有一个rehash method

翻版→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文档中的例子中,有趣的行为。哈希使用密钥kk.hash值记录事物。如果你有一个数组作为键并且你改变了数组,你也可以改变数组的hash值;结果是Hash仍然将该数组作为键,但是Hash无法将该数组作为键找到,因为它将在桶中查找新的值,但该数组将存储在旧的hash值。但是,如果你使用Hash,它会突然能够再次找到它的所有密钥,并且衰老消失。非数组键会出现类似的问题:你只需要改变键值,使其值改变,并且包含该键值的哈希值会变得混乱,并在你输入rehash之前四处移动。

Set class内部使用散列来存储其成员,并且成员被用作散列的键。所以,如果你改变了一个成员,那么Set会感到困惑。如果Set有一个rehash方法,那么你可以通过用rehash掌握设置头部的方向来解决问题,以敲入某种意义;唉,Set中没有这样的方法。但是,您可以猴子修补自己在:

class Set 
    def rehash 
     @hash.rehash 
    end 
end 

然后你可以改变键,上设置,和你的delete(以及各种其他方法,如member?)调用rehash将正常工作。

相关问题