我在 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?
和hash
方法,用于比较,意思是“即使它们不是相同的内存对象,它们是否是实际上相同的东西”。例如,String就是这样做的,这就是为什么你可以定义两个变量为“foo”,它们被认为是相等的,即使它们不是同一个对象。
在Ruby 1.8.6中,Hash类没有这样做,因此当在哈希对象上调用.eql?
和.hash
方法时(.hash方法与Hash数据类型无关 - 它类似于校验和类型的哈希),它会回退到使用Object基类中定义的方法,该方法只是简单地说“它是否是相同的内存对象”。
对于哈希对象,==
和===
运算符已经实现了我想要的功能,即如果它们的内容相同,则两个哈希是相同的。我已经覆盖了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,我的版本的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:一些研究表明,“^”是按位异或运算符。当我们有两个输入时,异或运算返回1,如果输入不同(即对于0,0和1,1返回0,并且对于0,1和1,0返回1)。
所以,起初我认为这意味着
result ^= key.hash
是缩写,代表
result = result ^ key.hash
换句话说,对结果的当前值和另一件事情进行异或运算,然后将其保存在结果中。但我仍然不太理解这个逻辑。我认为^运算符可能与指针有关,因为在变量上调用它可以正常工作,而在变量的值上调用它则无法正常工作:例如
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的实现似乎太慢了。有些困难...
1.8.6
发布已经超过10年了,9年前更新了1.8.7
版本,所有的1.8.x
版本在4年前就已经到达了生命周期终点。你为什么还要关心它呢? - spickermann