我有兴趣实现一个自定义的相等方法,用于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的最后一个实例.相反,我没有看到'做比较'调试行,并且数组保持相同的长度.
笔记:
===a.uniq{|x| [x.a, x.b]},但我不喜欢这个解决方案,它使代码看起来很混乱.它使用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)
你可以在的文档Array#uniq中找到答案(由于某种原因,在的文档Enumerable#uniq中没有提到):
它使用它们
hash和eql?方法来比较值以提高效率。
hash和的合同eql?如下:
hash对于被认为相等的对象,返回一个Integer必须相同的值,但对于不相等的对象,它不一定必须不同。这意味着不同的哈希值意味着对象肯定不相等,但相同的哈希值并不能告诉您任何信息。理想情况下,hash还应该能够抵抗意外和故意的碰撞。eql?是值相等,通常比它更严格==但不那么严格equal?,它或多或少是同一性:只有在将对象与其自身进行比较时才equal?应该返回。trueuniq?使用与哈希表、哈希集等相同的技巧来加速查找:
eql?。| 归档时间: |
|
| 查看次数: |
178 次 |
| 最近记录: |