我有兴趣在 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]} 长期完成了它,但我不喜欢这个解决方案,它使代码看起来很困惑。
  • 最佳答案



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

    所以你应该覆盖 eql? ( that is == ) 和 hash
    更新:

    我无法完全解释为什么会这样,但是覆盖 hash== 不起作用。我想这是由 uniq 在 C 中实现的方式造成的:

    来自:array.c(C 方法):
    所有者:阵列
    能见度:公开
    行数: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;
    }
    

    您可以通过使用 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 代替:
    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>]
    

    更新2:

    实际上,在覆盖方面,如果覆盖 ==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>]
    

    关于ruby - Ruby `uniq` 方法用于相等性检查的什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54461734/

    10-12 17:16