有什么方法可以指定如何比较 Ruby 2.6.0 中的 .difference 函数的对象数组?

Is there any way to specify how to compare of array of objects for .difference function in Ruby 2.6.0?

我正在尝试比较一组外部定义的对象。我希望我能够做一个简单的 .difference,一个在 Ruby 2.6.0 中引入的函数,但在查看它之后:https://ruby-doc.org/core-2.6/Array.html#method-i-difference 我不确定我可以指定自定义比较。

好的,假设我们有一个简单的 Object Num

# Pretend we don't have access to this, just for reference
class Num
  def initialize(val)
    @val = val
  end

  def val
    @val
  end
end

我有两个数组,一个是另一个的子集。我想找到子集丢失了什么。在下面的示例中,我希望差异是值为 3 的对象,因为它不存在于子集中。

all = [Num.new(1), Num.new(2), Num.new(3)]
subset = [Num.new(1), Num.new(2)]

默认的 .difference 函数在两个对象之间使用 .eql? 进行比较,因此差异不会给出预期的结果:

all.difference(subset)
=> [#<Num:0x00007fcae19e9540 @val=1>, #<Num:0x00007fcae19e9518 @val=2>, #<Num:0x00007fcae19e94f0 @val=3>]

我能够创建自己的自定义 hacky 解决方案来正确地给我想要的值:

def custom_difference(all, subset)
  diff = all.reject { |all_curr|
    subset.find{ |subset_curr|
      subset_curr.val == all_curr.val
    } != nil
  }
end

custom_difference(all, subset)
=> [#<Num:0x00007fcae19e94f0 @val=3>]

但我想知道是否可以利用现有的 .difference 函数,我也尝试这样使用以覆盖两个对象的比较方式:

all.difference(subset) { |a, b|
  a.val <=> b.val
}
=> [#<Num:0x00007fcae19e9540 @val=1>, #<Num:0x00007fcae19e9518 @val=2>, #<Num:0x00007fcae19e94f0 @val=3>]

但这对调整比较发生的方式没有任何作用(AFAIK)我做错了什么吗?这是不可能的吗? :'(

您想简单地覆盖对象上的 #eql?

 class Num
  def initialize(val)
    @val = val
  end

  def val
    @val
  end

  def eql?(comp)
    @val == comp.val
  end
end

现在,如果您尝试:

all = [Num.new(1), Num.new(2), Num.new(3)]
subset = [Num.new(1), Num.new(2)]
all.difference(subset) => [#<Num:0x00007fa7f7171e60 @val=3>]

如果您不想按照 Aleksei Matiushkin 的描述将 eql? 添加到 class(例如,如果您想对不同的事物使用多个标准),则无法重用 #difference。做你正在做的几乎就是你需要做的,尽管 Array#include? 是 O(N^2),所以我喜欢把 Set 放在那里:

Set.new(subset.map(&:val)).then { |s| all.reject { |x| s === x.val } }
# => [#<Num:0x00007febd32330e0 @val=3>]

或者,作为一种新方法:

module ArrayWithDifferenceBy
  refine Array do
    def difference_by(other)
      other_set = Set.new(other.map { |x| yield x })
      self.reject { |x| other_set.include?(yield x) }
    end
  end
end

module TestThis
  using ArrayWithDifferenceBy
  all = [Num.new(1), Num.new(2), Num.new(3)]
  subset = [Num.new(1), Num.new(2)]
  all.difference_by(subset, &:val)
end
# => [#<Num:0x00007febd32330e0 @val=3>]