How to sum properties of the objects within an array in Ruby
Solution 1
array.map(&:cash).inject(0, &:+)
or
array.inject(0){|sum,e| sum + e.cash }
Solution 2
In Ruby On Rails you might also try:
array.sum(&:cash)
Its a shortcut for the inject business and seems more readable to me.
http://api.rubyonrails.org/classes/Enumerable.html
Solution 3
#reduce
takes a block (the &:+
is a shortcut to create a proc/block that does +
). This is one way of doing what you want:
array.reduce(0) { |sum, obj| sum + obj.cash }
Solution 4
Most concise way:
array.map(&:cash).sum
If the resulting array from the map has nil items:
array.map(&:cash).compact.sum
Solution 5
If start value for the summation is 0, then sum alone is identical to inject:
array.map(&:cash).sum
And I would prefer the block version:
array.sum { |a| a.cash }
Because the Proc from symbol is often too limited (no parameters, etc.).
(Needs Active_Support)
Spike Fitsch
Updated on July 09, 2022Comments
-
Spike Fitsch almost 2 years
I understand that in order to sum array elements in Ruby one can use the inject method, i.e.
array = [1,2,3,4,5]; puts array.inject(0, &:+)
But how do I sum the properties of objects within an object array e.g.?
There's an array of objects and each object has a property "cash" for example. So I want to sum their cash balances into one total. Something like...
array.cash.inject(0, &:+) # (but this doesn't work)
I realise I could probably make a new array composed only of the property cash and sum this, but I'm looking for a cleaner method if possible!
-
Theo almost 12 years
#reduce
is an alias for#inject
in 1.9+, btw. -
Michael Kohl almost 12 years+1 for not iterating over
array
twice. The alias is also there in 1.8.7 btw. -
Michael Kohl almost 12 yearsYou are right about the symbol argument, but if
array
can be empty, you want the argument:[].inject(:+) #=> nil
,[].inject(0, :+) #=> 0
unless you want to deal with thenil
separately. -
Michael Kohl almost 12 yearsThis goes over
array
twice though, which might not be advisable if there are lots of elements. Why not just use a proper block forinject
? Alsoreduce/inject
directly takes a symbol argument, no need forSymbol#to_proc
:-) -
tokland almost 12 yearsas Michael says that's more space-efficient that map+reduce, but at the cost of modularity (small in this case, no need to say). In Ruby 2.0 we can have both thanks to laziness:
array.lazy.map(&:cash).reduce(0, :+)
. -
Nerian almost 12 yearsI wonder why there is such an alias. They have got the same length.
-
Michael Kohl almost 12 years@Nerian: In Smalltalk this was called
inject:into:
whereas several other languages call foldsreduce
(e.g. Clojure, Common Lisp, Perl, Python). The aliases are there to accomodate people with different backgrounds. Same formap
/collect
. -
tokland almost 12 yearsnote that you don't need to send a block,
inject
knows what to do with a symbol:inject(0, :+)
-
tokland almost 12 yearsYuri, I +1'd your answer, but the second snippet doesn't look good, better a functional:
array.inject(0) { |sum, e| sum + e.cash }
-
Yuri Barbashov almost 12 yearsi thought it might be a hash my fault)
-
Dennis about 10 yearsIf you're using Rails, this is the way to go.
-
Dennis about 10 yearsNote that if your array is the result of some kind of filtering on an ActiveRecord object, e.g.
@orders = Order.all; @orders.select { |o| o.status == 'paid' }.sum(&:cost)
, then you can also get the same result with a query:@orders.where(status: :paid).sum(:cost)
. -
dgmora over 8 yearsIf the records are not stored in the DB, the sum will be 0, where inject would work.
-
Augustin Riedinger over 8 yearsMore on @Dennis comment: if you are using Rails 4.1+, you can't
array.sum(&:cash)
on an activerecord relation, because it want's to make an ActiveRecord sum like so:array.sum(:cash)
which is massively different (SQL vs. Ruby). You'll have to convert it into an array to make it work again:array.to_a.sum(&:cash)
. Quite nasty! -
Danny over 8 years@AugustinRiedinger if possible, it is preferred to do sql sum vs ruby sum, no?
-
Augustin Riedinger over 8 yearsIt depends on the use case: Say you need to load the array of objects on the page, doing Ruby calculation will avoid querying
select sum(field) from table
but if you only need the sum value, for sureselect sum(field) from table
is faster thanselect * from table
parsed in objects and then summed in Ruby.