2012-04-28 84 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將正常工作。