我想对实际上相同但不相同的对象进行分组:
class MyClass
attr_accessor :value
def initialize(value)
@value = value
end
def ==(other)
(@value - other.value).abs < 0.0001
end
end
在与我的实现相关的精度方面,可以将两个相差0.0001的值视为相同:
MyClass.new(1.0) == MyClass.new(1.00001)
# => true
我希望它们属于同一组:
[MyClass.new(1.0), MyClass.new(1.00001)].group_by(&:value)
# => {1.0=>[#<MyClass:0x0000000d1183e0 @value=1.0>], 1.00001=>[#<MyClass:0x0000000d118390 @value=1.00001>]}
group_by
使用什么比较?可以使用内置group_by
来遵循自定义==
方法吗?还是为此需要自定义group_by
方法?
答案 0 :(得分:1)
TL; DR在我看来,这个问题是因为group_by实际上没有在任何地方检查相等性。它生成哈希,并使用数组的元素作为键。
长话短说
我在这里的第一个猜测是它正在做类似my_arr.map(&:value).group_by { |i| i }
的事情,这意味着它将检查2个float的相等性,而不是2个MyClasses。为了测试这一点,我在浮点数上重新定义了==
,并在我们对==
的两个定义中添加了调试puts语句。有趣的是,没有印刷任何东西。因此,我继续浏览the documentation for group_by
,并查看了源代码:
static VALUE
enum_group_by(VALUE obj)
{
VALUE hash;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
hash = rb_hash_new();
rb_block_call(obj, id_each, 0, 0, group_by_i, hash);
OBJ_INFECT(hash, obj);
return hash;
}
注意rb_block_call
的最后一个参数-这是一个哈希。这向我暗示,在后台,红宝石正在这样做:
def group_by(&:block)
h = {}
self.each do |ele|
key = block_given? ? block.call(ele) : ele
h[key] ||= []
h[key].push(ele)
end
end
从散列中获取密钥时,似乎未调用==
,因此这种重新定义==
的尝试没有达到您想要的目的。解决该问题的方法如下:
[MyClass.new(1.0), MyClass.new(1.00001)].group_by { |i| i.value.round(2) }
答案 1 :(得分:1)
分组依据使用hash
和eql?
来确定Hash
中的键是否相同。所以这样的事情应该起作用:
class MyClass
attr_accessor :value
def initialize(value)
@value = value
end
def ==(other)
(@value - other.value).abs < 0.0001
end
def hash
rounded_value.hash
end
def eql?(other)
self == other
end
def rounded_value
@value.round(3)
end
end
在将键标记为相等时,请检查是否为您提供了必需的精度。否则,请改用rounded_value
方法。
以下是关于红宝石中等于运算符/方法的一个很好的总结:What's the difference between equal?, eql?, ===, and ==?