How to combine two hash arrays based on hash value?
How to do it:
first_array = [ {:count=>nil, :date=>"Jan 31"}, {:count=>nil, :date=>"Feb 01"}, {:count=>nil, :date=>"Feb 02"}, {:count=>nil, :date=>"Feb 03"}, {:count=>nil, :date=>"Feb 04"}, {:count=>nil, :date=>"Feb 05"} ] second_array = [ {:count=>12, :date=>"Feb 01"}, {:count=>2, :date=>"Feb 02"}, {:count=>2, :date=>"Feb 05"} ] In it:
result = [ {:count=>nil, :date=>"Jan 31"}, {:count=>12, :date=>"Feb 01"}, {:count=>2, :date=>"Feb 02"}, {:count=>nil, :date=>"Feb 03"}, {:count=>nil, :date=>"Feb 04"}, {:count=>2, :date=>"Feb 05"} ] I found similar questions on SO, but none of them were as simple as this. Probably the method / block combination that I should use, I don't know.
TL DR
Use each_with_object enumerated:
first_array.each_with_object(second_array){ |e,a| a << e if a.none?{ |i| i[:date] == e[:date] } } Long answer: r
One approach that I find useful is to list each_with_object . The whole method can be written as follows:
first_array.each_with_object(second_array){ |e,a| a << e if a.none?{ |i| i[:date] == e[:date] } } irb(main):025:0> pp first_array.each_with_object(second_array){ |e,a| a << e if a.none?{ |i| i[:date] == e[:date] } } [{:count=>12, :date=>"Feb 01"}, {:count=>2, :date=>"Feb 02"}, {:count=>2, :date=>"Feb 05"}, {:count=>nil, :date=>"Jan 31"}, {:count=>nil, :date=>"Feb 03"}, {:count=>nil, :date=>"Feb 04"}] This approach will also work when we need to compare multiple values, for example. a.none?{ |i| i[:date] == e[:date] and i[:location] == e[:location] }
When the elements of an array are hashes with two keys, and one of the keys is unique, converting the array to a hash is another solution. First, we convert both arrays to hashes, and then combine the first with the second, and then convert it back to a hash array.
def array_of_hashed_dates_to_hash(a_of_h); a_of_h.each_with_object({}){ |e,h| h[e[:date]] = e[:count] }; end array_of_hashed_dates_to_hash(first_array).merge(array_of_hashed_dates_to_hash(second_array)).map{|e| {date: e.first, count: e.last}} irb(main):039:0> pp array_of_hashed_dates_to_hash(first_array).merge(array_of_hashed_dates_to_hash(second_array)).map{|e| {date: e.first, count: e.last}} [{:date=>"Jan 31", :count=>nil}, {:date=>"Feb 01", :count=>12}, {:date=>"Feb 02", :count=>2}, {:date=>"Feb 03", :count=>nil}, {:date=>"Feb 04", :count=>nil}, {:date=>"Feb 05", :count=>2}] The first way seems more efficient:
#!/usr/bin/ruby -Ku require 'benchmark' first_array = [ {:count=>nil, :date=>"Jan 31"}, {:count=>nil, :date=>"Feb 01"}, {:count=>nil, :date=>"Feb 02"}, {:count=>nil, :date=>"Feb 03"}, {:count=>nil, :date=>"Feb 04"}, {:count=>nil, :date=>"Feb 05"} ] second_array = [ {:count=>12, :date=>"Feb 01"}, {:count=>2, :date=>"Feb 02"}, {:count=>2, :date=>"Feb 05"} ] n = 1000 def array_of_hashed_dates_to_hash(a_of_h) a_of_h.each_with_object({}){ |e,h| h[e[:date]] = e[:count] } end Benchmark.bm(20) do |x| x.report("Compare by Hash value (each_with_object)") do n.times do first_array.each_with_object(second_array){ |e,a| a << e if a.none?{ |i| i[:date] == e[:date] } } end end x.report("Convert to Hashes and merge") do n.times do first_array_hash = array_of_hashed_dates_to_hash(first_array) second_array_hash = array_of_hashed_dates_to_hash(second_array) first_array_hash.merge(second_array_hash).map{|e| {date: e.first, count: e.last}} end end end user system total real Compare by Hash value (each_with_object) 0.000000 0.000000 0.000000 ( 0.008223) Convert to Hashes and merge 0.020000 0.000000 0.020000 ( 0.012077) This does what you need:
result = first_array.map do |first_hash| c = second_array.select do |second_hash| second_hash[:date] == first_hash[:date] end if c.empty? first_hash else c.first end end NB: Here, I assume that first_array always has hashes with nil :count , and second_array does not, as in your example.
My suggestion is to use: date as the hash key and: count as its value. if all you need, counting on a date, it is better to have something like:
result_hash = { "Jan 31" => nil, "Feb 01" => 12, ...} btw, if the required result is required, I propose this solution:
all = first_array + second_array result_hash = {} all.each do |x| result_hash[x[:date]] = x[:count] end result = [] result_hash.each_pair do |x, y| result << {:count => y, :date => x} end This solution will give priority to non nil values.
def hash_merge (h1, h2)
h3 = {}
h1.each do | k, v |
h3 [k] = h1 [k]. eql? (h2 [k])? v: (h1 [k] .nil?? h2 [k]: h1 [k])
end
return h3
end
result = []
first_array.each do | h1 |
h2 = {}
second_array.each do | h |
if h1 [: date] .eql? (h [: date])
h2 = h
break
end
end
result.push hash_merge (h1, h2)
end
p result