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

请参阅下面的示例

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]} ????? 

这与arrays的行为不同。 (如果我们删除.to_set ,我们将获得预期的s = [[1, 2]] 。)这是一个错误吗?

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

问题有两个主要原因:

  1. 你正在修改Set的元素而不知道它。
  2. 标准Ruby 集实现为哈希。

结果是你正在修改内部Hash的密钥,而Hash不知道它,并且将糟糕的Hash混淆到不知道它有什么密钥。 Hash类有一个rehash方法 :

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文档中包含的示例中的有趣行为。 哈希使用密钥kk.hash值跟踪事物。 如果您将数组作为键并更改数组,则也可以更改数组的hash值; 结果是Hash仍然将该数组作为键,但Hash将无法将该数组作为键查找,因为它将在存储桶中查找新的hash值,但该数组将位于存储桶中旧的hash值。 但是,如果你重新散列哈希,它会突然能够再次找到它的所有键,并且衰老消失了。 非数组键也会出现类似的问题:你只需改变键,使其hash值发生变化,包含该键的Hash会混淆并在你丢失之前四处游荡。

Set类在内部使用Hash来存储其成员,并且成员用作哈希的键。 因此,如果您更改成员,该集将会混淆。 如果Set有一个rehash方法,那么你可以通过重新拍打将头部设置在头部以便将某种感觉敲入其中来解决问题。 唉,Set中没有这样的方法。 但是,你可以自己修补:

 class Set def rehash @hash.rehash end end 

然后你可以更改密钥,在Set上调用rehash ,你的delete (和其他各种方法,如member? )将正常工作。