How to sum properties of the objects within an array in Ruby

42,409

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)

Share:
42,409
Spike Fitsch
Author by

Spike Fitsch

Updated on July 09, 2022

Comments

  • Spike Fitsch
    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
    Theo almost 12 years
    #reduce is an alias for #inject in 1.9+, btw.
  • Michael Kohl
    Michael Kohl almost 12 years
    +1 for not iterating over array twice. The alias is also there in 1.8.7 btw.
  • Michael Kohl
    Michael Kohl almost 12 years
    You 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 the nil separately.
  • Michael Kohl
    Michael Kohl almost 12 years
    This goes over array twice though, which might not be advisable if there are lots of elements. Why not just use a proper block for inject? Also reduce/inject directly takes a symbol argument, no need for Symbol#to_proc :-)
  • tokland
    tokland almost 12 years
    as 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
    Nerian almost 12 years
    I wonder why there is such an alias. They have got the same length.
  • Michael Kohl
    Michael Kohl almost 12 years
    @Nerian: In Smalltalk this was called inject:into: whereas several other languages call folds reduce (e.g. Clojure, Common Lisp, Perl, Python). The aliases are there to accomodate people with different backgrounds. Same for map/collect.
  • tokland
    tokland almost 12 years
    note that you don't need to send a block, inject knows what to do with a symbol: inject(0, :+)
  • tokland
    tokland almost 12 years
    Yuri, 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
    Yuri Barbashov almost 12 years
    i thought it might be a hash my fault)
  • Dennis
    Dennis about 10 years
    If you're using Rails, this is the way to go.
  • Dennis
    Dennis about 10 years
    Note 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
    dgmora over 8 years
    If the records are not stored in the DB, the sum will be 0, where inject would work.
  • Augustin Riedinger
    Augustin Riedinger over 8 years
    More 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
    Danny over 8 years
    @AugustinRiedinger if possible, it is preferred to do sql sum vs ruby sum, no?
  • Augustin Riedinger
    Augustin Riedinger over 8 years
    It 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 sure select sum(field) from table is faster than select * from table parsed in objects and then summed in Ruby.