'group_by'使用的Ruby比较

时间:2018-08-13 13:42:15

标签: ruby group-by

我想对实际上相同但不相同的对象进行分组:

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

2 个答案:

答案 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)

分组依据使用hasheql?来确定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 ==?