【问题标题】:What does the Ruby `uniq` method use for equality checking?Ruby `uniq` 方法用于相等性检查是什么?
【发布时间】:2019-01-31 13:31:08
【问题描述】:

我对在 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]} 长期完成了它,但我不喜欢这个解决方案,它使代码看起来很混乱。

【问题讨论】:

  • 我们是否假设这两个答案都没有帮助?

标签: ruby


【解决方案1】:

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

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>]

【讨论】:

  • 更新了我的答案
  • “我无法完全解释为什么会这样,但覆盖 hash== 不起作用。” – 您自己写了答案:“它使用 hasheql? 方法比较值以提高效率。”
【解决方案2】:

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

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

hasheql?的合约如下:

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

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

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

【讨论】:

    猜你喜欢
    • 2020-02-28
    • 2016-12-21
    • 2018-09-01
    • 1970-01-01
    • 2016-12-24
    • 2016-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多