danynl
danynl

Reputation: 299

ruby Compare values between hashes in different arrays

I have two different arrays, each consisting of different hashes.

new_array1 =    [
                    {:index=>4, :column=>0, :ID=>"ABC"},
                    {:index=>4, :column=>1, :ID=>"XYZ"},
                    {:index=>4, :column=>2, :ID=>"BCD-1547"}
                ]


new_array2 =    [
                    {:index=>4, :column=>0, :ID=>"ABC"},
                    {:index=>4, :column=>1, :ID=>"IJK"},
                    {:index=>4, :column=>2, :ID=>"BCD-1547"}
                ]

I want to compare the values of the :ID keys in new_array1 vs the values in new_array2 only if the :index and :column values are the same.

ie)   
if (new_array1[0][:index] == new_array2[0][:index]) and (new_array1[0][:column] == new_array2[0][:column])
   if(new_array1[0][:ID] == new_array2[0][:ID])
         # do something
   end
end

Is there a way to loop through all the hashes in both arrays and find the match? Or perhaps a more elegant way to do this in ruby?

Upvotes: 2

Views: 1379

Answers (3)

Kris
Kris

Reputation: 19938

This will return an array of matching hashes:

res = new_array1.inject([]) { |memo, hash| memo << hash if new_array2.any? { |hash2| hash[:ID] == hash2[:ID] && hash[:index] == hash2[:index] && hash[:column] == hash2[:column] }; memo } 
# => [{:index=>4, :column=>0, :ID=>"ABC"}, {:index=>4, :column=>1, :ID=>"XYZ"}, {:index=>4, :column=>2, :ID=>"BCD-1547"}]

res.each do |hash|
  # do something
end

If an item in new_array1 has the same index, column and ID keys as any item in new_array2 it will be included.

You could also simpify if these are the only keys in the hashes by using == to compare equality:

res = new_array1.inject([]) { |memo, hash| memo << hash if new_array2.any? { |hash2| hash == hash2 }; memo }

The inject method, aliased and also known as reduce, takes a collection and creates a new value from it, each time the block given to inject is called it is given the next element of the collection and the return value of the previous block (the first time the block is called it is given the seed value passed to inject). This allows you to build up a value similar to recursion.

There are some examples of inject here: Need a simple explanation of the inject method

The any? method will return true as soon as the given block returns true for any of the given collection elements. If the block never returns true then any? returns false. So:

[0,0,0,1,0].any? { |num| num == 1 } # => true
[0,0,0,0,0].any? { |num| num == 1 } # => false

Upvotes: 2

Cary Swoveland
Cary Swoveland

Reputation: 110675

If all hashes have the same three keys and no other keys it's simply

new_array1 & new_array2
  #=> [{:index=>4, :column=>0, :ID=>"ABC"},
  #    {:index=>4, :column=>2, :ID=>"BCD-1547"}] 

If the hashes may have other keys as well, you can write the following.

new_array1 = [{:index=>4, :column=>0, :ID=>"ABC",      :pet=>"cat"},
              {:index=>4, :column=>1, :ID=>"XYZ",      :bet=>"red"},
              {:index=>4, :column=>2, :ID=>"BCD-1547", :met=>"Betty"}]

new_array2 = [{:index=>4, :column=>0, :ID=>"ABC",      :tree=>"maple"},
              {:index=>4, :column=>1, :ID=>"IJK",      :colour=>"blue"},
              {:index=>4, :column=>2, :ID=>"BCD-1547", :car=>"beemer"}]

keys = [:index,:column,:ID]
h1 = new_array1.each_with_object({}) { |g,h| h[g.select { |k,_| keys.include? k }] = g }
  #=> {{:index=>4, :column=>0, :ID=>"ABC"}=>
  #      {:index=>4, :column=>0, :ID=>"ABC", :pet=>"cat"},
  #    {:index=>4, :column=>1, :ID=>"XYZ"}=>
  #      {:index=>4, :column=>1, :ID=>"XYZ", :bet=>"red"},
  #    {:index=>4, :column=>2, :ID=>"BCD-1547"}=>
  #      {:index=>4, :column=>2, :ID=>"BCD-1547", :met=>"Betty"}}
h2 = new_array2.each_with_object({}) { |g,h| h[g.select { |k,_| keys.include? k }] = g }
  #=> {{:index=>4, :column=>0, :ID=>"ABC"}=>
  #      {:index=>4, :column=>0, :ID=>"ABC", :tree=>"maple"},
  #    {:index=>4, :column=>1, :ID=>"IJK"}=>
  #      {:index=>4, :column=>1, :ID=>"IJK", :colour=>"blue"},
  #    {:index=>4, :column=>2, :ID=>"BCD-1547"}=>
  #      {:index=>4, :column=>2, :ID=>"BCD-1547", :car=>"beemer"}} 
(h1.keys & h2.keys).map { |k| [h1[k], h2[k]] }
  #=> [[{:index=>4, :column=>0, :ID=>"ABC", :pet=>"cat"},
  #     {:index=>4, :column=>0, :ID=>"ABC", :tree=>"maple"}],
  #    [{:index=>4, :column=>2, :ID=>"BCD-1547", :met=>"Betty"},
  #     {:index=>4, :column=>2, :ID=>"BCD-1547", :car=>"beemer"}]] 

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

[new_array1, new_array2].map do |a|
  a.group_by { |e| [e[:index], e[:column]] }
end.reduce do |f, l|
  f.merge(l) { |_, f, l| [f.first[:ID], l.first[:ID]] }
end
# => {
#     [ 4, 0 ] => [
#         [0] "ABC",
#         [1] "ABC"
#     ],
#     [ 4, 1 ] => [
#         [0] "XYZ",
#         [1] "IJK"
#     ],
#     [ 4, 2 ] => [
#         [0] "BCD-1547",
#         [1] "BCD-1547"
#     ]
# }

Put do_something unless f.first[:ID] == l.first[:ID] instead of [f.first[:ID], l.first[:ID]] in the last clause to do whatever you want.

Upvotes: 0

Related Questions