1

I have 3 array of hashes:

a = [{name: 'Identifier', value: 500}, {name: 'Identifier2', value: 50 }] b = [{name: 'Identifier', value: 500}, {name: 'Identifier2', value: 50 }] c = [{name: 'Identifier', value: 500}, {name: 'Identifier2', value: 50 }] 

and I have to merge them into one, based on the name prop of each identifier, so the result will be:

d = [{name: 'Identifier', value: 1500 }, {name: 'Identifier2', value: 150}] 

Is there a smart ruby way of doing this, or do I have to do create another hash where the keys are the identifiers, the values the values and then transform it into an array?

Thank you.

1
  • Is the order of identifiers in all arrays the same? That is {name: 'Identifier', value: ...} always the first element in all 3 arrays, {name: 'Identifier2', value: ... } always the second? Commented Aug 26, 2020 at 16:24

4 Answers 4

2

When the values of a single key in a collection of hashes are to be totaled I usually begin by constructing a counting hash:

h = (a+b+c).each_with_object({}) do |g,h| h[g[:name]] = (h[g[:name]] || 0) + g[:value] end #=> {"Identifier"=>1500, "Identifier2"=>150} 

Note that if h does not have a key g[:name], h[g[:name]] #=> nil, so:

h[g[:name]] = (h[g[:name]] || 0) + g[:value] = (nil || 0) + g[:value] = 0 + g[:value] = g[:value] 

We may now easily obtain the desired result:

h.map { |(name,value)| { name: name, value: value } } #=> [{:name=>"Identifier", :value=>1500}, # {:name=>"Identifier2", :value=>150}] 

If desired these two expressions can be chained:

(a+b+c).each_with_object({}) do |g,h| h[g[:name]] = (h[g[:name]] || 0) + g[:value] end.map { |(name,value)| { name: name, value: value } } #=> [{:name=>"Identifier", :value=>1500}, # {:name=>"Identifier2", :value=>150}] 

Sometimes you might see:

h[k1] = (h[k1] || 0) + g[k2] 

written:

(h[k1] ||= 0) + g[k2] 

which expands to the same thing.

Another way to calculate h, which I would say is more "Ruby-like", is the following.

h = (a+b+c).each_with_object(Hash.new(0)) do |g,h| h[g[:name]] += g[:value] end 

This creates the hash represented by the block variable h using the form of Hash::new that takes an argument called the default value:

h = Hash.new(0) 

All this means is that if h does not have a key k, h[k] returns the default value, here 0. Note that

h[g[:name]] += g[:value] 

expands to:

h[g[:name]] = h[g[:name]] + g[:value] 

so if h does not have a key g[:name] this reduces to:

h[g[:name]] = 0 + g[:value] 

If you were wondering why h[g[:name]] on the left of the equality was not replaced by 0, it is because that part of the expression employs the method Hash#[]=, whereas the method Hash#[] is used on he right. Hash::new with a default value only concerns Hash#[].

Sign up to request clarification or add additional context in comments.

Comments

0

You can do everything in ruby !

Here is a solution to your problem :

d = (a+b+c).group_by { |e| e[:name] }.map { |f| f[1][0].merge(value: f[1].sum { |g| g[:value] }) } 

I encourage you to check the Array Ruby doc for more information: https://ruby-doc.org/core-2.7.0/Array.html

Comments

0

I am assuming that the order of identifiers in all arrays is the same. That is {name: 'Identifier', value: ...} always the first element in all 3 arrays, {name: 'Identifier2', value: ... } always the second, etc. In this simple case, a simple each_with_index is a simple and clear solution:

d = [] a.each_with_index do |hash, idx| d[idx] = {name: hash[:name], value: a[idx][:value] + b[idx][:value] + c[idx][:value] } end # Or a more clear version using map: a.each_with_index do |hash, idx| d[idx] = {name: hash[:name], value: [a, b, c].map { |h| h[idx][:value] }.sum } end 

Comments

0

A couple different ways, avoiding any finicky array-indexing and the like, (also functionally, since you've added the tag):

grouped = (a + b + c).group_by { _1[:name] } name_sums = grouped.transform_values { |hashes| hashes.map { _1[:value] }.sum } 
name_vals = (a + b + c).map { Hash[*_1.values_at(:name, :value)] } name_sums = name_vals.reduce { |l, r| l.merge(r) { |k, lval, rval| lval + rval } } 

in either case, finish it off with:

name_sums.map { |name, value| { name: name, value: value } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.