Pergunta

I want to get the union/intersect/difference from two arrays of hashes for example:

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}]

however when I want to only compare based on the names and ignore the ages without needing to remove them from the hashes for example:

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

In this case i'm not getting the results that I want b/c the ages are different.

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}]

Is there a way to get the union/intersect/difference and ignore the age key?

edit: for a better example:

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

Thanks in advance for the help!

Foi útil?

Solução

Here's a quick and dirty way of getting the union:

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

However, I would recommend creating your own subclass of Hash so you can safely override eql? as Cary Swoveland points out is what the set-like operators rely on. Note that you also need to limit the hash method to only provide the hashing function on the name field.

class Guy < Hash

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

  def hash
    self[:name].hash
  end

end

Then these Guy objects will work in all of the set operations:

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}]

Outras dicas

For the difference:

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

Intersection

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

All three class Array methods referenced use Hash#eql? to compare two elements that are both hashes. (Hash#eql? checks to see if the hashcodes for the two hashes are equals.) Therefore, we need only (temporarily) redefine 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

Note that this is one of those few situations where self must be explicit. If we wrote:

[:name] == h[:name]

instead of:

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

Ruby would assume that we are comparing the array [:name] with h[:name].

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top