I want to group objects that are virtually the same but not identical like in this case:
class MyClass
attr_accessor :value
def initialize(value)
@value = value
end
def ==(other)
(@value - other.value).abs < 0.0001
end
end
With the precision relevant for my implementation, two values differing by 0.0001 can be regarded identical:
MyClass.new(1.0) == MyClass.new(1.00001)
# => true
I want these to be in the same group:
[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>]}
What comparison is used for group_by? Can the built in group_by be made to honor the custom == method, or is a custom group_by method required for this?
TL;DR It appears to me that this issue is because group_by doesn't actually check equality anywhere. It generates the hash, and uses the elements of the array as keys.
Long story:
My first guess here was that it was doing something like my_arr.map(&:value).group_by { |i| i }, which would mean that it would be checking the equality of 2 floats instead of 2 MyClasses. To test this, I redefined == on a float, and added debugging puts statements to both of our definitions of ==. Interestingly, nothing was printed. So, I went on over to the documentation for group_by, and looked at the source code:
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;
}
Notice the last argument to rb_block_call -- It's a hash. This hinted to me that under the hood, ruby is doing this:
def group_by(&:block)
h = {}
self.each do |ele|
key = block_given? ? block.call(ele) : ele
h[key] ||= []
h[key].push(ele)
end
end
When getting a key from a hash, it seems that == is not called, and so this attempt to redefine == didn't do what you wanted. A way to fix this would be like so:
[MyClass.new(1.0), MyClass.new(1.00001)].group_by { |i| i.value.round(2) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With