I've modified your example array as follows:
arr = [
{"May 21"=> [{:applications_one=> 20}, {:applications_two=> 0}]},
{"Jun 21"=> [{:applications_two=> 0}, {:applications_one=> 15}]},
{"Jul 21"=> [{:applications_one=> 6}, {:applications_three=>4}]},
{"Aug 21"=> [{:applications_one=> 1}, {:applications_two=> 2}]},
{"Sep 21"=> [{:applications_one=> 8}, {:applications_two=> 11}]},
{"Oct 21"=> [{:applications_three=>7}, {:applications_one=> 1}]}
]
My purpose in doing so is to show that the code I suggest below:
- does not require the hashes within the values (arrays) of the date strings to be ordered;
- does not require the names of the keys of the hashes within the values (e.g.,
:applications_one
) to be known in advance; and
- allows the values (arrays) of the date strings to contain any number of hashes.
The values of interest can be computed as follows.
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|
date, a = g.flatten
h[:dates] << date
a.each do |f|
label, value = f.flatten
h[label] << value
end
end
#=> {
# :dates=>["May 21", "Jun 21", "Jul 21", "Aug 21", "Sep 21", "Oct 21"],
# :applications_one=>[20, 15, 6, 1, 8, 1],
# :applications_two=>[0, 0, 2, 11],
# :applications_three=>[4, 7]
}
The calculations proceed as follows.
Initially,
h = Hash.new { |h,k| h[k] = [] }
#=> {}
The first hash is then passed to the block (held by the block variable g
) and block operations are performed.
g = {"May 21"=> [{:applications_one=> 20}, {:applications_two=> 0}]}
date, a = g.flatten
#=> ["May 21", [{:applications_one=>20}, {:applications_two=>0}]]
# therefore, date #=> "May 21" and
# a #=> [{:applications_one=>20}, {:applications_two=>0}]]
See Hash#flatten.
h[:dates] << date
# now h #=> {:dates=>["May 21"]}
a.each do |f|
puts "f=#{f}"
label, value = f.flatten
puts "label=#{label}, value=#{value}"
h[label] << value
puts "h=#{h}\n"
end
The following is displayed:
f={:applications_one=>20}
label=applications_one, value=20
h={:dates=>["May 21"], :applications_one=>[20]}
f={:applications_two=>0}
label=applications_two, value=0
h={:dates=>["May 21"], :applications_one=>[20], :applications_two=>[0]}
The next element of arr
is now passed to the block and the block calculations are performed.
g #=> {"Jun 21"=> [{:applications_two=> 0}, {:applications_one=> 15}]}
date, a = g.flatten
#=> ["Jun 21", [{:applications_two=>0}, {:applications_one=>15}]]
# therefore, date #=> "Jun 21" and
# a #=> [{:applications_two=>0}, {:applications_one=>15}]
h[:dates] << date
# now h #=> {:dates=>["May 21", "Jun 21"]}
a.each do |f|
puts "f=#{f}"
label, value = f.flatten
puts "label=#{label}, value=#{value}"
h[label] << value
puts "h=#{h}\n"
end
The following is displayed.
f={:applications_two=>0}
label=applications_two, value=0
h={:dates=>["May21", "Jun21"], :applications_one=>[20],
:applications_two=>[0, 0]}
f={:applications_one=>15}
label=applications_one, value=15
h={:dates=>["May21", "Jun21"], :applications_one=>[20, 15],
:applications_two=>[0, 0]}
The remaining calculations are similar.
Note that when the second element of arr
was passed to the block,
h #=> {:dates=>["May 21"], :applications_one=>[20],
# :applications_two=>[0]} g[:date] #=> "Jun 21"
the following calculation makes perfect sense:
h[:dates] << date
#=> ["May 21"] << "Jun 21"
#=> ["May 21", "Jun 21"]
By contrast, when the first element of arr
was passed to the block, the following calculation was performed when h #=> {}
, and therefore h[:dates] #=> nil
:
h[:dates] << date
h #=> {:dates=>["May 21"]}
You might wonder why this works, since nil
does not have a method <<
. It's because of the way h
was defined:
h = Hash.new { |h,k| h[k] = [] }
See the form of Hash::new that takes a block (and therefore no argument).
What this means is that if h
does not have a key k
and may be altered by operation, the assignment h[k] = []
is performed first. (This does not apply to, for example, m = h[k]; m #=> nil
, as h
is not being altered.)
After having processed
{"May 21"=> [{:applications_one=> 20}, {:applications_two=> 0}]}
the hash we are building is as follows:
h #=> {:dates=>["May 21"], :applications_one=>[20],
# :applications_two=>[0]}
For the example given in the question the following is returned:
{:dates=>["May 21", "Jun 21", "Jul 21", "Aug 21"],
:applications_one=>[20, 15, 8, 1],
:applications_two=>[0, 0, 11, 2]}