Есть ли способ указать, как сравнивать массив объектов для функции.difference в Ruby 2.6.0?

Я пытаюсь сравнить массив внешне определенных объектов. Я надеялся, что смогу сделать простой .difference, функция, которая была представлена ​​в Ruby 2.6.0, но после ее просмотра: https://ruby-doc.org/core-2.6/Array.html Я не уверен, что могу указать пользовательский сравнение.

Хорошо, если у нас есть простой объект 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>]

Я смог создать свое собственное хакерское решение, чтобы правильно дать мне нужные значения:

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). Я делаю что-то не так? Это просто невозможно?:'(

2 ответа

Если вы не хотите добавлять eql? к классу, как описано Алексеем Матюшкиным (например, если вы хотите использовать несколько критериев для разных вещей), нет возможности повторно использовать #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>]

Вы хотите просто переопределить #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>]
Другие вопросы по тегам