首页 > 解决方案 > 'group_by' 使用的 Ruby 比较

问题描述

我想对实际上相同但不相同的对象进行分组,例如在这种情况下:

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方法?

标签: rubygroup-by

解决方案


TL;DR 在我看来,这个问题是因为 group_by 实际上并没有在任何地方检查相等性。它生成哈希,并使用数组的元素作为键。

很长的故事:

我在这里的第一个猜测是它正在做类似的事情my_arr.map(&:value).group_by { |i| i },这意味着它将检查 2 个浮点数而不是 2 个 MyClasses 的相等性。为了测试这一点,我重新定义==了一个浮点数,并在我们的两个定义中添加了调试 puts 语句==。有趣的是,没有打印任何内容。因此,我继续查看的文档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) }

推荐阅读