How to Merge Array of Hashes to Get Hash of Arrays of Values

How to merge array of hashes to get hash of arrays of values

Take your pick:

hs.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}

hs.map(&:to_a).flatten(1).reduce({}) {|h,(k,v)| (h[k] ||= []) << v; h}

I'm strongly against messing with the defaults for hashes, as the other suggestions do, because then checking for a value modifies the hash, which seems very wrong to me.

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

Convert array of hashes to single hash with values as keys

This should do what you want

countries.each_with_object({}) { |country, h| h[country[:country].to_sym] = country[:cost] }
=> {:england=>12.34, :scotland=>56.78}

Merge hash of arrays into array of hashes

I suggest writing the code to permit arbitrary numbers of attributes. It's no more difficult than assuming there are two (:name and :surname), yet it provides greater flexibility, accommodating, for example, future changes to the number or naming of attributes:

def squish(h)
keys = h.keys.map(&:to_s)
h.values.transpose.map { |a| keys.zip(a).to_h }
end

h = { name:    ["John", "Jane", "Chris"],
surname: ["Doe", "Doe", "Smith"],
age: [22, 34, 96]
}

squish(h)
#=> [{"name"=>"John", "surname"=>"Doe", "age"=>22},
# {"name"=>"Jane", "surname"=>"Doe", "age"=>34},
# {"name"=>"Chris", "surname"=>"Smith", "age"=>96}]

The steps for the example above are as follows:

b = h.keys
#=> [:name, :surname, :age]
keys = b.map(&:to_s)
#=> ["name", "surname", "age"]
c = h.values
#=> [["John", "Jane", "Chris"], ["Doe", "Doe", "Smith"], [22, 34, 96]]
d = c.transpose
#=> [["John", "Doe", 22], ["Jane", "Doe", 34], ["Chris", "Smith", 96]]
d.map { |a| keys.zip(a).to_h }
#=> [{"name"=>"John", "surname"=>"Doe", "age"=>22},
# {"name"=>"Jane", "surname"=>"Doe", "age"=>34},
# {"name"=>"Chris", "surname"=>"Smith", "age"=>96}]

In the last step the first value of b is passed to map's block and the block variable is assigned its value.

a = d.first
#=> ["John", "Doe", 22]
e = keys.zip(a)
#=> [["name", "John"], ["surname", "Doe"], ["age", 22]]
e.to_h
#=> {"name"=>"John", "surname"=>"Doe", "age"=>22}

The remaining calculations are similar.

How to merge array of hashes based on hash value but not merge values instead override

Maybe you could just call Array#uniq with a block on email key of the concatenation (Array#+) of the two arrays:

(ary1 + ary2).uniq { |h| h[:email] }

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

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

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.

how do I merge array of hashes summing by the value?

Let's group by id and map each result to a structure where the pages are the sum of all the pages for that id

array.
group_by { |item| item[:id] }.
map do |id, items|
page_sum = items.sum { |i| i[:pages] }
Hash[:id, id, :book, items.first[:book], :pages, page_sum]
end


Related Topics



Leave a reply



Submit