3

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]

4 Answers 4

4

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:

  • depending on requirements, sort may not be required
  • the second method could be used regardless of whether the values of :data are defined in parallel
  • the second method uses the form of Hash::new which takes an argument (the default value) which here is zero. This is sometimes called a counting hash. See the doc for details.
Sign up to request clarification or add additional context in comments.

1 Comment

Interesting! I've never thought of using Array#transpose this way.
2

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

Comments

1

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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.