Ruby 1.8.6 Array #uniq没有删除重复的哈希值

时间:2017-11-14 16:36:59

标签: arrays ruby uniq

我在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"}]

谁能告诉我这里发生了什么?

编辑:我可以编写一个不太聪明的uniqifier,使用include?如下:

uniqed = []
arr.each do |hash|
  unless uniqed.include?(hash)
    uniqed << hash
  end
end;false
uniqed
=> [{:foo=>"bar"}]

这会产生正确的结果,这使uniq的失败更加神秘。

编辑2:关于发生了什么的一些注意事项,可能只是为了我自己的清晰度。正如@ Ajedi32在评论中指出的那样,unqify的失败来自于这两个元素是不同的对象。有些类定义用于比较的eql?hash方法,意思是“这些实际上是相同的,即使它们不是内存中的同一个对象”。例如,字符串就是这样做的,这就是为什么你可以将两个变量定义为“foo”并且它们被认为彼此相等,即使它们不是同一个对象。

Hash类在Ruby 1.8.6中执行此操作,因此在哈希对象(.hash方法)上调用.eql?.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:一些研究表明“^”是按位异或运算符。当我们有两个输入时,如果输入不同,则XOR返回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的实现似乎太慢了。有点卡住......

2 个答案:

答案 0 :(得分:2)

出于效率原因,Array#uniq不会使用==甚至===来比较值。根据{{​​3}}:

  

它使用hash和eql来比较值?提高效率的方法。

(注意我在这里链接了2.4.2的文档。虽然1.8.6的文档没有包含这个语句,但我相信它仍然适用于那个版本的Ruby。)

在Ruby 1.8.6中the docs,所以他们回退到使用neither Hash#hash nor Hash#eql? are implementedObject#hash

  

Equality - 在Object级别,==仅在obj和other是同一个对象时才返回true。通常,在后代类中重写此方法以提供特定于类的含义。

     

[...]

     

如果obj和anObject具有相同的值,则eql?方法返回true。由Hash用于测试成员的相等性。对于Object类的对象,eql?==同义。

因此,根据Array#uniq,这两个哈希值是不同的对象,因此是唯一的。

要解决此问题,您可以尝试自己定义Object#eql?Hash#hash。如何做到这一点的细节留给读者练习。但是,您可以参考Hash#eql?

答案 1 :(得分:0)

如何在Javascript中使用JSON stringify并将其解析回来?

require 'json'
arr.map { |x| x.to_json}.uniq.map { |x| JSON.parse(x) }

1.8.6可能不支持json方法,请使用支持的方法。