Reputation: 145
I have this array of arrays in the data attribute for each activity corresponding to the class attendance by hour.
I need the total attendance of each activity by hour. Is it possible to group and sum it?
Example: The correct result for the hour 7 is 50+60 = 110
[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
Expected result:
:data=>[["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]
Upvotes: 3
Views: 1046
Reputation: 3449
How to group and sum values in array of arrays with the same structure
You should do it like this :
[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
Try this one:
act.map{|h| h[:data]}.flatten(1).group_by(&:first).map { |k,v| [k, v.map(&:last).inject(:+)] }
# => [["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]
Hope this will help you
Upvotes: 2
Reputation: 110675
Case 1: The values of :data
are arrays of the same size whose elements (two-element arrays) are ordered the same by their first elements
arr = [{ :name=>"cardio", :data=>[["06", 999], ["07", 50], ["08", 0]] },
{ :name=>"swimming", :data=>[["06", 0], ["07", 60], ["08", 0]] }]
a, b = arr.map { |h| h[:data].transpose }.transpose
#=> [[["06", "07", "08"], ["06", "07", "08"]], [[999, 50, 0], [0, 60, 0]]]
{ :data=>a.first.zip(b.transpose.map { |col| col.reduce(:+) }) }
#=> {:data=>[["06", 999], ["07", 110], ["08", 0]]}
Case 2: The values of :data
are arrays which may differ in size and whose elements (two-element arrays) may not be ordered the same by their first element
arr = [{ :name=>"cardio", :data=>[["05", 999], ["07", 50], ["08", 0]] },
{ :name=>"swimming", :data=>[["08", 300], ["04", 33], ["07", 60]] }]
{ :data=>arr.flat_map { |g| g[:data] }.
each_with_object(Hash.new(0)) { |(f,v),h| h[f] += v }.
sort.
to_a }
#=> {:data=>[["04", 33], ["05", 999], ["07", 110], ["08", 300]]}
Note:
sort
may not be required:data
are defined in parallelUpvotes: 4
Reputation: 8888
actions=[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
{:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
actions.map{|act| act[:data]}.reduce({}) do |acc, data|
acc.merge(data.to_h) {|k, v1, v2| v1 + v2}
end.to_a
This solution takes advantage of the fact that Ruby's Hash
keeps the insertion order.
The merit of this algorithm is that it allows missing keys in either data array.
Upvotes: 0
Reputation: 103774
Given:
act=[{:name=>"cardio",
:data=>[["06", 999], ["07", 50], ["08", 0], ["09", 154], ["10", 1059], ["11", 90], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
{:name=>"swimming",
:data=>[["06", 0], ["07", 60], ["08", 0], ["09", 0], ["10", 90], ["11", 50], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]
You can do:
> act.map{ |h| h[:data] }
.flatten(1)
.group_by{ |h,n| h }
.map { |k,v| [k, v.map(&:last).sum] }
=> [["06", 999], ["07", 110], ["08", 0], ["09", 154], ["10", 1149], ["11", 140], ["12", 30], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]
Or,
> act.map{|h| h[:data]}
.flatten(1)
.each_with_object(Hash.new(0)) {|e,h| h[e[0]]+=e[1]}.to_a
works too.
Upvotes: 1