我在 ruby 1.8.6 控制台中有这个数组:
arr = [{:foo => "bar"}, {:foo => "bar"}]
两个元素彼此相等:
arr[0] == arr[1]
=> true
#just in case there's some "==" vs "===" oddness...
arr[0] === arr[1]
=> true
但是, arr.uniq 不会删除重复项:
arr.uniq
=> [{:foo=>"bar"}, {:foo=>"bar"}]
谁能告诉我这是怎么回事?
编辑:我可以写一个不是很聪明的唯一标识符,它使用include?
如下:
uniqed = []
arr.each do |hash|
unless uniqed.include?(hash)
uniqed << hash
end
end;false
uniqed
=> [{:foo=>"bar"}]
这会产生正确的结果,从而导致失败uniq
更神秘了。
编辑2:一些关于正在发生的事情的注释,可能只是为了我自己的清晰起见。正如 @Ajedi32 在评论中指出的那样,统一化失败的原因是这两个元素是不同的对象。一些类定义eql?
and hash
用于比较的方法表示“这些实际上是同一件事,即使它们在内存中不是同一个对象”。例如,String 就是这样做的,这就是为什么您可以将两个变量定义为“foo”,并且它们彼此相等,即使它们不是同一个对象。
哈希类doesn't在 Ruby 1.8.6 中执行此操作,所以当.eql?
and .hash
在哈希对象上调用(.hash 方法与哈希数据类型无关 - 它就像哈希的校验和类型),它回退到使用 Object 基类中定义的方法,该方法只是说“它是内存中的同一个对象”。
The ==
and ===
对于哈希对象,运算符已经做了我想要的事情,即如果两个哈希值的内容相同,则它们是相同的。我已经覆盖了Hash#eql?
使用这些,像这样:
class Hash
def eql?(other_hash)
self == other_hash
end
end
但是,我不知道如何处理Hash#hash
:也就是说,我不知道如何生成一个校验和,对于内容相同的两个哈希值来说,该校验和是相同的,而对于具有不同内容的两个哈希值来说,该校验和总是不同的。
@Ajedi32 建议我看看 Rubinius 的实现Hash#hash
方法在这里https://github.com/rubinius/rubinius/blob/master/core/hash.rb#L589 https://github.com/rubinius/rubinius/blob/master/core/hash.rb#L589,我的 Rubinius 实现版本如下所示:
class Hash
def hash
result = self.size
self.each do |key,value|
result ^= key.hash
result ^= value.hash
end
return result
end
end
这似乎确实有效,尽管我不知道“^=”运算符的作用,这让我有点紧张。而且,它非常慢——根据一些原始基准测试,大约慢了 50 倍。这可能会导致使用速度太慢。
编辑3:一些研究表明“^”是按位异或运算符。当我们有两个输入时,如果输入不同,则 XOR 返回 1(即,对于 0,0 和 1,1 返回 0,对于 0,1 和 1,0 返回 1)。
所以,一开始我以为这意味着
result ^= key.hash
是简写
result = result ^ key.hash
换句话说,在 result 的当前值和其他值之间进行异或,然后将其保存在 result 中。但我仍然不太明白其中的逻辑。我认为 ^ 运算符可能与指针有关,因为在变量上调用它可以工作,而在变量的值上调用它则不起作用:例如
var = 1
=> 1
var ^= :foo
=> 14904
1 ^= :foo
SyntaxError: compile error
(irb):11: syntax error, unexpected tOP_ASGN, expecting $end
因此,在变量上调用 ^= 是可以的,但不是变量的值,这让我认为这与引用/取消引用有关。
后来的 Ruby 实现也有 Hash#hash 方法的 C 代码,而 Rubinius 的实现似乎太慢了。有点卡住了...