2

I understand that in ruby we can sum the elements in an array like this

[1,2,3].sum 
[<Object a: 1>, <Object a:2>].sum(&:a)

How about running a sum on multiple attributes in the array of objects. For example:

arr = [<Object a:1, b:2, ..., n:10>, <Object a:1, b:2, ..., n:10>, ... <nth Object>]

What is the most efficient way to get the sum of multiple attributes a, b, ... n?

I have tried these methods:

sum_a = arr.sum(&:a)
sum_b = arr.sum(&:b)
a, b = 0, 0
arr.each do |obj|
  a += obj.a
  b += obj.b
end

I am wondering if there is a better way to go about this.

3 Answers 3

2

You could create your own method for this:

def sum_multiple(arr, *keys)
  arr.each_with_object(keys.zip([0] * keys.size).to_h) do |o,obj|
    keys.each {|k| obj[k] += o.public_send(k)}
  end
end

Usage as:

class A 
  attr_reader :a,:b,:c 

  def initialize(a,b,c)
    @a,@b,@c = a,b,c
  end 
end 

arr = 10.times.map {|i| A.new(i,i*2,i*3)}

sum_multiple(arr,:a)
#=> {:a=>45}
sum_multiple(arr,:a,:b)
#=> {:a=>45, :b=>90}
sum_multiple(arr,:a,:b,:c)
#=> {:a=>45, :b=>90, :c=>135}
"Sum of everything: #{sum_multiple(arr,:a,:b,:c).sum(&:last)}" 
#=> "Sum of everything: 270"

Example

Note: If this is rails then you should look into combinations of group by and sum and put this on the database.

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

2 Comments

how have you defined the A class here?
@lacostenycoder while the definition of the class isn't all that important since the methods #a, #b, and #c could be antyhing it does lend to reproducibility I have added the definition
1

If you want to sum only some of the key values you can do:

[{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}].flat_map{|h| h.slice(:a, :b).values}.sum

Or if you want to sum all values then just

[{a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}].flat_map(&:values).sum

For objects who might not be hashes

arr.sum {|o| [:a, :b, :c].sum{ |key| o.send key} }

6 Comments

I think the OP is looking for something more like a.reduce {|memo,h| memo.merge(h) { |_,o,n| o + n }}
But every object does not have merge method.
@HazhirDerakhshi not every object has a values method either
@engineersmnky Exactly.
@lacostenycoder yeah it is a bit unclear because this "How about running a sum on multiple attributes in the array of objects" can have multiple interpretations including an interim per pass sum, but the code examples seem more like a sum per attribute either way +1 for the effort.
|
1

Let's begin by creating an array of instances.

class C
  def initialize(a,b,c)
    @a, @b, @c = a, b, c
  end
end

arr = [[1,2,3], [4,5,6], [7,8,9]].map { |a| C.new(*a) }
  #=> [#<C:0x00005a4282c2d980 @a=1, @b=2, @c=3>,
  #    #<C:0x00005a4282c2d5c0 @a=4, @b=5, @c=6>,
  #    #<C:0x00005a4282c2d3b8 @a=7, @b=8, @c=9>] 

then

ivs = arr.first.instance_variables
  #=> [:@a, :@b, :@c]
arr.map { |i| ivs.map { |v| i.instance_variable_get(v) } }. 
    transpose.
    map(&:sum)
 #=> [12, 15, 18] 

The steps are as follows.

a = arr.map { |i| ivs.map { |v| i.instance_variable_get(v) } }
  #=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] 
b = a.transpose
  #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] 
c = b.map(&:sum) 
  #=> [12, 15, 18] 

If labels are desired:

ivs = arr.first.instance_variables
ivs.zip(arr.map {|i| ivs.map {|v| i.instance_variable_get(v)}}. 
            transpose.
            map(&:sum)).
    to_h
  #=> {:@a=>12, :@b=>15, :@c=>18}

If only sums of some instance variables were desired, say @a and @c, just change ivs:

ivs = [:@a, :@c]
ivs.zip(arr.map {|i| ivs.map {|v| i.instance_variable_get(v)}}. 
            transpose.
            map(&:sum)).
    to_h
  #=> {:@a=>12, :@c=>18}

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.