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

AJF*_*day 3 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
Run Code Online (Sandbox Code Playgroud)

我期望uniq方法调用Foo#==,并删除Foo的最后一个实例.相反,我没有看到'做比较'调试行,并且数组保持相同的长度.

笔记:

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

mrz*_*asa 6

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

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

所以你应该覆盖eql?(==)和hash

更新:

我无法完全解释为什么会这样,但是压倒一切hash并且==不起作用.我想这是因为这种方式uniq在C中实现:

来自:array.c(C方法):所有者:数组可见性:public行数:20

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;
}
Run Code Online (Sandbox Code Playgroud)

你可以通过使用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>]
Run Code Online (Sandbox Code Playgroud)

或者Struct改为使用:

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>]
Run Code Online (Sandbox Code Playgroud)

UPDATE2:

实际上,如果你覆盖==或者覆盖它就不一样了eql?.当我覆盖eql?它按预期工作:

class Foo
  attr_accessor :a, :b

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

  def eql?(other)
    (@a == other.a && @b == other.b)
  end

  def hash
    [a, b].hash
  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
#=> [#<Foo:0x0000562e483bff70 @a=1, @b=1>,
#<Foo:0x0000562e483bff48 @a=1, @b=2>,
#<Foo:0x0000562e483bff20 @a=2, @b=1>,
#<Foo:0x0000562e483bfef8 @a=2, @b=2>]
Run Code Online (Sandbox Code Playgroud)


Jör*_*tag 6

你可以在的文档Array#uniq中找到答案(由于某种原因,在的文档Enumerable#uniq中没有提到):

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

hash和的合同eql?如下:

  • hash对于被认为相等的对象,返回一个Integer必须相同的值,但对于不相等的对象,它不一定必须不同。这意味着不同的哈希值意味着对象肯定不相等,但相同的哈希值并不能告诉您任何信息。理想情况下,hash还应该能够抵抗意外和故意的碰撞。
  • eql?是值相等,通常比它更严格==但不那么严格equal?,它或多或少是同一性:只有在将对象与其自身进行比较时才equal?应该返回。true

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

  1. 比较哈希值。计算哈希值通常应该很快。
  2. 如果哈希值相同,则且仅在此时使用 进行双重检查eql?