Как получить объединение/пересечение/разницу из двух массивов хэшей и игнорировать некоторые ключи

Я хочу получить объединение/пересечение/разницу из двух массивов хэшей, например:

array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1', :age => 45},{:name =>'Guy3', :age => 45}]

...

p array1 - array2 

=> [{:name=>"Guy2", :age=>45}]


p array2 - array1
=> [{:name=>"Guy3", :age=>45}]


p array1 | array2 
=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3", :age=>45}]

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

array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1', :age => 46},{:name =>'Guy3', :age => 45}]

В этом случае я не получаю желаемых результатов, потому что возраст разный.

array1 - array2 

=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}]

array2 - array1
=> [{:name=>"Guy1", :age=>46}, {:name=>"Guy3", :age=>45}]

array1 | array2 
=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy1", :age=>46}, {:name=>"Guy3", :age=>45}]

Есть ли способ получить объединение/пересечение/разницу и игнорировать ключ возраста?

редактировать: для лучшего примера:

array1 = [{:name =>'Guy1', :age => 45},{:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1'},{:name =>'Guy3'}]

p array1 - array2
p array2 - array1
p array1 | array2
p array1 & array2

Заранее спасибо за помощь!


person Seth Pollack    schedule 09.05.2014    source источник
comment
Каков ожидаемый результат, особенно какое значение для :age?   -  person sawa    schedule 09.05.2014
comment
Ожидаемый результат был бы таким, как если бы :age не существовало.   -  person Mark Thomas    schedule 09.05.2014
comment
ожидаемый результат будет таким же, как и в первом примере.   -  person Seth Pollack    schedule 09.05.2014


Ответы (3)


Вот быстрый и грязный способ получить объединение:

(array1 + array2).uniq{|a| a[:name]}

Тем не менее, я бы рекомендовал создать собственный подкласс Hash, чтобы вы могли безопасно переопределить eql?, как указывает Кэри Своувленд, это то, на что полагаются операторы, подобные множествам. Обратите внимание, что вам также необходимо ограничить метод hash, чтобы предоставить функцию хэширования только для поля имени.

class Guy < Hash

  def eql?(other_hash)
    self[:name] == other_hash[:name]
  end

  def hash
    self[:name].hash
  end

end

Тогда эти Guy объекты будут работать во всех операциях набора:

array1 = [ Guy[name:'Guy1', age: 45], Guy[name:'Guy2', age: 45] ]
array2 = [ Guy[name:'Guy1', age: 46], Guy[name:'Guy3', age: 45] ]

array1 - array2
#=> [{:name=>"Guy2", :age=>45}]

array2 - array1
#=> [{:name=>"Guy3", :age=>45}]

array1 | array2
#=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3", :age=>
45}]

array1 & array2
#=> [{:name=>"Guy1", :age=>45}]
person Mark Thomas    schedule 09.05.2014
comment
Даже создание подклассов может быть опасным, но да, это лучше, чем возиться с классом Hash. Если f и g являются объектами Guy, можно непреднамеренно использовать f.eql?(g) вместо f == g (изменение eql? не влияет на =), что может привести к довольно неприятной ошибке. - person Cary Swoveland; 09.05.2014

Для разницы:

diff_arr = array1.map{|a| a[:name]} - array2.map{|a| a[:name]}

diff_arr.map{|a| array1.map{|s| s if s[:name] == a }}.flatten.compact

Перекресток

intersec_arr = array1.map{|a| a[:name]} & array2.map{|a| a[:name]}

intersec_ar.map{|a| array1.map{|s| s if s[:name] == a }}.flatten.compact
person cvibha    schedule 09.05.2014

Все три упомянутых метода класса Array используют Hash# eql? для сравнения двух элементов, которые оба являются хэшами. (Hash#eql? проверяет, равны ли хэш-коды для двух хэшей.) Следовательно, нам нужно только (временно) переопределить Hash#eql?.

array1 = [{:name =>'Guy1', :age => 45}, {:name =>'Guy2', :age => 45}]
array2 = [{:name =>'Guy1'},             {:name =>'Guy3'}] 

class Hash
  alias old_eql? eql?
  def eql?(h) self[:name] == h[:name] end
end

array1 - array2
  #=> [{:name=>"Guy2", :age=>45}]
array2 - array1
  #=> [{:name=>"Guy3"}]
array1 | array2
  #=> [{:name=>"Guy1", :age=>45}, {:name=>"Guy2", :age=>45}, {:name=>"Guy3"}]
array1 & array2
  #=> [{:name=>"Guy1", :age=>45}]

class Hash
  alias eql? old_eql? # Restore eql?
  undef_method :old_eql?
end

Обратите внимание, что это одна из тех немногих ситуаций, когда self должен быть явным. Если бы мы написали:

[:name] == h[:name]

вместо:

self[:name] == h[:name]

Ruby предполагает, что мы сравниваем массив [:name] с h[:name].

person Cary Swoveland    schedule 09.05.2014
comment
Я бы не рекомендовал этот подход из-за того, что основной класс Hash требует исправления обезьяны. Однако, если OP должен был создать новый класс, например. class Guys < Hash и переопределить eql?, то я с энтузиазмом рекомендую этот подход! (Возможно, вы можете отредактировать/исправить этот ответ?) - person Mark Thomas; 09.05.2014
comment
Кроме того, это не работает. Я думаю, вы что-то забываете (см. мой ответ для подсказки). - person Mark Thomas; 09.05.2014
comment
Хорошие моменты, @Mark. Я выдвинул это решение главным образом потому, что считал его образовательным. Когда я писал это, я знал, что иду по тонкому льду; Я должен был хотя бы добавить несколько предостерегающих слов. Даже создание подкласса Hash может быть опасным, как я упомяну в комментарии к вашему ответу, но в меньшей степени, чем возиться с Hash. Я рассмотрю ваш второй пункт в отдельном комментарии. - person Cary Swoveland; 09.05.2014
comment
@Mark, я заметил, что в документации для всех трех методов [Array#-](), Array#| и Array#& все упоминаются. Он сравнивает элементы, используя их хэш и eql? методы повышения эффективности. Я знаю, что Hash@eql? использует Hash#hash, но мне было непонятно, что методы Array будут зависеть от Hash#hash (как и Hash.eql?), и мне было лень проверять исходный код. Я пришел к выводу, что предложение было просто плохо сформулировано. Результаты теста были в порядке без переопределения Hash#hash. Знаете ли вы (или кто-либо другой), нужно ли переопределять Hash#hash, и если да, то почему? - person Cary Swoveland; 09.05.2014
comment
Да, Hash#hash нужно переопределить. У меня по другому не работало (в 1.9.3 irb). Вот ссылка: javieracero.com/blog/the- ключ-к-рубиновым-хэшам-это-eql-хэш - person Mark Thomas; 09.05.2014