Merging Arrays of Hashes

Rails mapping array of hashes onto single hash

You could compose Enumerable#reduce and Hash#merge to accomplish what you want.

input = [{"testPARAM1"=>"testVAL1"}, {"testPARAM2"=>"testVAL2"}]
input.reduce({}, :merge)
is {"testPARAM2"=>"testVAL2", "testPARAM1"=>"testVAL1"}

Reducing an array sort of like sticking a method call between each element of it.

For example [1, 2, 3].reduce(0, :+) is like saying 0 + 1 + 2 + 3 and gives 6.

In our case we do something similar, but with the merge function, which merges two hashes.

[{:a => 1}, {:b => 2}, {:c => 3}].reduce({}, :merge)
is {}.merge({:a => 1}.merge({:b => 2}.merge({:c => 3})))
is {:a => 1, :b => 2, :c => 3}

How to merge two arrays of hashes

uniq would work if you concatenate the arrays in reverse order:

(b + a).uniq { |h| h[:key] }
#=> [
# {:key=>1, :value=>"bar"},
# {:key=>1000, :value=>"something"},
# {:key=>2, :value=>"baz"}
# ]

It doesn't however preserve the order.

Merge an array of hashes by key-value pair

The more-or-less generic and extendable variant would be:

input.
group_by { |h| h['abc_id'] }.
map do |k, v|
v.reduce do |acc, arr|
# use `+` instead of `|` to save duplicates ⇓⇓⇓
acc.merge(arr) { |_, v1, v2| Array === v1 ? v1 | v2 : v1 }
end
end
#⇒ [{"abc_id"=>"1234", "def_id"=>["33", "44"]},
# {"abc_id"=>"5678", "def_id"=>["11", "22", "55", "66"]}]

How to add and merge values from different array of hashes in ruby on rails

Using two helper methods for a DRY code and using Enumerable#sum, Enumerable#group_by, Hash#merge, Hash#transform_values and also other methods you can find in the documentation.

I'm using also Object#then here.

def sum_price_tax(ary)
ary.first.merge ary.then { |ary| { "Price" => ary.sum { |h| h["Price"].to_f }, "Tax" => ary.sum { |h| h["Tax"].to_f} } }
end

def group_and_sum(array_of_hash)
array_of_hash.group_by { |h| h["Date"] }.transform_values { |ary| sum_price_tax(ary) }
end

After the methods are defined, you can do:

a1 = group_and_sum(array_of_hash1)
a2 = group_and_sum(array_of_hash2)
a1.map { |k, v| v.merge(a2[k] || {}) { |h, old_val, new_val| old_val.is_a?(Float) ? old_val - new_val : old_val } }

#=> [{"Date"=>"2019-07-01", "Country"=>"US", "Email"=>"sample1@gmail.com", "Price"=>2.2121007000000006, "Tax"=>6.254, "Name"=>"John"}, {"Date"=>"2019-06-30", "Country"=>"US", "Email"=>"sample3@gmail.com", "Price"=>17.854323, "Tax"=>7.12343}, {"Date"=>"2019-07-02", "Country"=>"UK", "Email"=>"sample4@gmail.com", "Price"=>4.0019787, "Tax"=>1.9798780000000002, "Name"=>"Sam"}]

In this way also the "Name" is present.


One way you could get rid of "Name" is using Object#tap and Hash#delete:

a1.map { |k, v| v.merge(a2[k] || {}) { |h, old_val, new_val| old_val.is_a?(Float) ? old_val - new_val : old_val  }.tap { |h| h.delete("Name") } }

#=> [{"Date"=>"2019-07-01", "Country"=>"US", "Email"=>"sample1@gmail.com", "Price"=>2.2121007000000006, "Tax"=>6.254}, {"Date"=>"2019-06-30", "Country"=>"US", "Email"=>"sample3@gmail.com", "Price"=>17.854323, "Tax"=>7.12343}, {"Date"=>"2019-07-02", "Country"=>"UK", "Email"=>"sample4@gmail.com", "Price"=>4.0019787, "Tax"=>1.9798780000000002}]

How to merge arrays of hashes with matching a column value

This answer only works if array2 doesn't contain any duplicate Plotting ID values. (If there are duplicate Plotting ID it still works, but it uses the last record of the once precent in the array.)

array1 = [{"Date" => "2019-01-01", "Plot ID" => 234}, {"Date" => "2019-01-01", "Plot ID" => 235}, {"Date" => "2019-01-01", "Plot ID" => 236}, {"Date" => "2019-01-01", "Plot ID" => 237}, {"Date" => "2019-01-01", "Plot ID" => 238}, {"Date" => "2019-01-01", "Plot ID" => 239}, {"Date" => "2019-01-01", "Plot ID" => 240}, {"Date" => "2019-01-01", "Plot ID" => 241}]
array2 = [{"Date" => "2019-01-01", "Plotting ID" => 234, "size"=> 20, "visit" => 10, "price" => 103}, {"Date" => "2019-01-01", "Plotting ID" => 500, "size"=> 40, "visit" => 22, "price" => 233}, {"Date" => "2019-01-01", "Plotting ID" => 236, "size"=> 25, "visit" => 34, "price" => 423}, {"Date" => "2019-01-01", "Plotting ID" => 600, "size"=> 79, "visit" => 55, "price" => 234}]

array2_lookup = array2.map(&:dup).map { |record| [record.delete('Plotting ID'), record] }.to_h
array2_lookup.default = { 'size' => 0, 'visit' => 0, 'price' => 0 }
pp array1.map { |record| array2_lookup[record['Plot ID']].merge(record) }
# [{"Date"=>"2019-01-01", "size"=>20, "visit"=>10, "price"=>103, "Plot ID"=>234},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>235},
# {"Date"=>"2019-01-01", "size"=>25, "visit"=>34, "price"=>423, "Plot ID"=>236},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>237},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>238},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>239},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>240},
# {"size"=>0, "visit"=>0, "price"=>0, "Date"=>"2019-01-01", "Plot ID"=>241}]

The above solution first loops over array2 and converts it to a hash, by deleting the key/value pair 'Plotting ID' from the hash and using the value as key. For this reason I've added the .map(&:dup) call, preventing the original hashes in array2 from mutating. If the hash mutation is not an issue for you you can simply remove this.

After creating the lookup hash I've added a default that is used when merging the hashes. All that is left to do now is loop through array1, lookup the record (if any) or use the default and merge that with the current element.

This answer leaves the keys somewhat scrambled, but since a hash is based on key lookup anyway (not key/value order) this shouldn't be a huge issue. If you'd like to have all keys in the same order you can do so by providing all the keys to the default, setting their values to nil (or any other value since they are overwritten):

array2_lookup.default = { 'Date' => nil, 'size' => 0, 'visit' => 0, 'price' => 0 }
# ... ^ added placeholder for ordering purposes
# [{"Date"=>"2019-01-01", "size"=>20, "visit"=>10, "price"=>103, "Plot ID"=>234},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>235},
# {"Date"=>"2019-01-01", "size"=>25, "visit"=>34, "price"=>423, "Plot ID"=>236},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>237},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>238},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>239},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>240},
# {"Date"=>"2019-01-01", "size"=>0, "visit"=>0, "price"=>0, "Plot ID"=>241}]

Ruby 2.7: How to merge a hash of arrays of hashes and eliminate the duplicates based on one key:value

Ruby has many ways to achieve this.

My first instinct is to group them by id it and pick only first item from the array.

h.values.flatten.group_by{|x| x["id"]}.map{|k,v| v[0]}

Much cleaner approach is to pick the distinct item based on id after flattening the array of hash which is what Cary Swoveland suggested in the comments

h.values.flatten.uniq { |h| h['id'] }

Merge two array of hashes

You can use this way:

first.zip(second).map { |f, s| f.merge(s) }
#=> [{:frontman=>"aaa", :category=>"bbb", :subcategory=>nil, ...}]

How to merge two array of hashes based on hash's value?

result_array = first_array.map do |first_hash| 
second_array.each do |second_hash|
if first_hash[:date] == second_hash[:date]
first_hash[:count] = second_hash[:count]
break
end
end
first_hash
end


Related Topics



Leave a reply



Submit