Ruby`uniq`方法用于相等性检查的功能是什么?

时间:2019-01-31 13:31:08

标签: ruby

我对实现自定义的相等方法以在Ruby中的对象数组中使用感兴趣。这是一个简单的示例:

class Foo

  attr_accessor :a, :b

  def initialize(a, b)
    @a = a
    @b = b
  end 

  def ==(other)
    puts 'doing comparison'
    @a == @a && @b == @b
  end

  def to_s
    "#{@a}: #{@b}"  
  end

end 

a = [
  Foo.new(1, 1),
  Foo.new(1, 2),
  Foo.new(2, 1),
  Foo.new(2, 2),
  Foo.new(2, 2)
]
a.uniq

我希望uniq方法调用Foo#==,并删除Foo的最后一个实例。相反,我看不到“进行比较”调试行,并且数组保持相同的长度。

注意:

  • 我正在使用ruby 2.2.2
  • 我尝试将方法定义为===
  • 我已经用a.uniq{|x| [x.a, x.b]}做了很长一段时间,但是我不喜欢这种解决方案,它会使代码看起来很混乱。

2 个答案:

答案 0 :(得分:5)

  

它使用哈希值和eql比较值?效率的方法。

https://ruby-doc.org/core-2.5.0/Array.html#method-i-uniq-3F

因此,您应该覆盖round_hour <- function(x) round(x + 20, -2) %% 2400 that is ==)和eql?

更新:

我无法完全解释为什么会这样,但是覆盖hashhash无效。我想这是由于==在C中实现的原因所致:

来自:array.c(C方法): Owner:数组 公开程度:公开 行数:20

uniq

您可以通过使用uniq的块版本来绕过该操作:

static VALUE
rb_ary_uniq(VALUE ary)
{
    VALUE hash, uniq;

    if (RARRAY_LEN(ary) <= 1)
        return rb_ary_dup(ary);
    if (rb_block_given_p()) {
        hash = ary_make_hash_by(ary);
        uniq = rb_hash_values(hash);
    }
    else {
        hash = ary_make_hash(ary);
        uniq = rb_hash_values(hash);
    }
    RBASIC_SET_CLASS(uniq, rb_obj_class(ary));
    ary_recycle_hash(hash);

    return uniq;
}

或改用> [Foo.new(1,2), Foo.new(1,2), Foo.new(2,3)].uniq{|f| [f.a, f.b]} => [#<Foo:0x0000562e48937cc8 @a=1, @b=2>, #<Foo:0x0000562e48937c78 @a=2, @b=3>]

Struct

UPDATE2:

实际上,在覆盖方面,您覆盖F = Struct.new(:a, :b) [F.new(1,2), F.new(1,2), F.new(2,3)].uniq # => [#<struct F a=1, b=2>, #<struct F a=2, b=3>] ==并不相同。当我覆盖eql?时,它按预期工作:

eql?

答案 1 :(得分:1)

您可以在the documentation of Array#uniq中找到答案(由于某些原因,the documentation of Enumerable#uniq中未提及):

  

它使用hasheql?方法比较值以提高效率。

hasheql?的合同如下:

  • hash返回一个Integer,对于被认为相等的对象,必须相同,但对于不相等的对象则不必相同。这意味着不同散列意味着对象绝对不相等,但是相同的哈希值不会告诉您任何信息。理想情况下,hash还应能够抵抗意外和故意的碰撞。
  • eql?是值相等,通常比==严格,但不如{或其他身份相同的equal?严格:equal?仅在以下情况下返回true您将对象与自身进行比较。

uniq?使用与哈希表,哈希集等相同的技巧来加快查找速度:

  1. 比较散列。计算散列通常应该很快。
  2. 如果散列相同,则只能使用eql?进行仔细检查。