620

I have an array of integers.

For example:

array = [123,321,12389]

Is there any nice way to get the sum of them?

I know, that

sum = 0
array.each { |a| sum+=a }

would work.

Mab879
  • 608
  • 16
  • 33
brainfck
  • 9,286
  • 7
  • 28
  • 29
  • 40
    Please note that Ruby 2.4+ has `array.sum` – dawg Jul 03 '17 at 18:58
  • 1
    Ruby 2.6 does not have it. Ruby giveth, Ruby taketh away, it seems. – Lori Jan 22 '19 at 20:38
  • 2
    @Lori hmm ? [link](https://docs.ruby-lang.org/en/2.6.0/Array.html#method-i-sum) – steenslag Jun 26 '19 at 15:22
  • 2
    Sorry. At that time I mistakenly believed I was using 2.6 because of a rbenv slip-up on my part. – Lori Jun 27 '19 at 00:15
  • If you need to supply a default value for when the `Array` is empty, like if you want to return a `Money` object instead of an `Integer`, you can do something like `array.sum( 0.to_money( "USD" ) )`. – Joshua Pinter Mar 05 '21 at 16:05

16 Answers16

858

For ruby >= 2.4 you can use sum:

array.sum

For ruby < 2.4 you can use inject:

array.inject(0, :+)

Note: the 0 base case is needed otherwise nil will be returned on empty arrays:

> [].inject(:+)
nil
> [].inject(0, :+)
0
Ngoral
  • 4,215
  • 2
  • 20
  • 37
jomey
  • 8,689
  • 2
  • 14
  • 4
  • 6
    How can I use this way to sum a attribute from object. My array [product1, product2] I want to sum product1.price + product2.price. Is it possible using array.inject(:+)? – Pablo Cantero Apr 27 '11 at 20:45
  • 1
    Pablo: You can do: array.map{|p| p.price}.inject(+:) – Diego Basch Jul 28 '11 at 20:51
  • 7
    You can use a similar trick with the map method: array.map(&:price).inject(:+) – markquezada Sep 08 '11 at 02:58
  • 101
    `array.map(&:price).inject(0, :+)` is a bit safer. It makes sure that if you have an empty list you get **0** instead of **nil**. – johnf Oct 04 '11 at 09:21
  • 11
    using array.map(...).inject(...) is inefficient, you will iterate through all data twice. Try `array.inject(0) { |sum, product| sum += product.price }` – everett1992 Apr 04 '13 at 06:11
  • 1
    @everett1992 In most cases, this is probably a premature optimisation. – Cameron Martin Aug 19 '14 at 15:12
  • 5
    @everett1992 and as it turns out, not even an optimisation at all. Doing it in two stages is consistently faster for me. https://gist.github.com/cameron-martin/b907ec43a9d8b9303bdc – Cameron Martin Aug 19 '14 at 15:24
  • @everett1992 `inject` handles forwarding the result itself. you should write `sum + product.price` instead of `sum += product.price`. If you really want to build an object by yourself (e.g. some array), you should consider [`each_with_object`](https://apidock.com/ruby/Enumerable/each_with_object) – Cadoiz Jul 28 '23 at 07:15
  • @PabloCantero for ruby >= 2.4, [sum](https://apidock.com/ruby/Array/sum) works like this: `array.sum(&:price)` (you can also use the pretzel-colon like Johnf suggested with `.map`. In rails btw [the `sum` method already exists like forever](https://apidock.com/rails/ActiveRecord/Calculations/ClassMethods/sum). – Cadoiz Jul 28 '23 at 07:18
645

Try this:

array.inject(0){ |sum, x| sum + x }

See Ruby's Enumerable Documentation

(note: the 0 base case is needed so that 0 will be returned on an empty array instead of nil)

Rimian
  • 36,864
  • 16
  • 117
  • 117
zenazn
  • 14,295
  • 2
  • 36
  • 26
304
array.reduce(0, :+)

While equivalent to array.inject(0, :+), the term reduce is entering a more common vernacular with the rise of MapReduce programming models.

inject, reduce, fold, accumulate, and compress are all synonymous as a class of folding functions. I find consistency across your code base most important, but since various communities tend to prefer one word over another, it’s nonetheless useful to know the alternatives.

To emphasize the map-reduce verbiage, here’s a version that is a little bit more forgiving on what ends up in that array.

array.map(&:to_i).reduce(0, :+)

Some additional relevant reading:

David Wolever
  • 148,955
  • 89
  • 346
  • 502
Evan
  • 7,396
  • 4
  • 32
  • 31
  • 12
    I agree, `reduce` tells me more of what the function does, but `inject` does sound much cooler. – everett1992 Apr 04 '13 at 06:13
  • 1
    The one comment I would make is that `reduce` and `map` as higher-order functions predate MapReduce. The inspiration runs the other way. And in the MapReduce sense, it's a somewhat different operation than a simple functional reduce, having implications for how different machines communicate. – acjay Apr 13 '15 at 14:01
  • 1
    Ken Iverson introduced the operator / called "reduction operator" in the programming language APL. Source: Iverson, Kenneth. 1962. A Programming Language. Wiley. Another source: "Notation as a Tool of Thought", 1979 ACM Turing Award Lecture, Kenneth E. Iverson, http://dl.acm.org/ft_gateway.cfm?id=1283935&type=pdf – Fernando Pelliccioni Jul 25 '16 at 01:50
118

Alternatively (just for comparison), if you have Rails installed (actually just ActiveSupport):

require 'activesupport'
array.sum
Mike Woodhouse
  • 51,832
  • 12
  • 88
  • 127
  • 13
    Newer versions of activesupport don't actually load all extensions by default. You'll want to either require just the sum module: `require 'active_support/core_ext/enumerable.rb'`, or require all of active support: `require 'active_support/all'`. More about it here: [API Docs](http://edgeguides.rubyonrails.org/active_support_core_extensions.html) – dcashman Mar 21 '12 at 20:42
  • 2
    Never mind that `activesupport` is a *massive* dependency to drag into a project to go from `array.inject(:+)` to `array.sum`. – user229044 Feb 11 '16 at 13:29
  • 1
    Nitpick to an otherwise good comment: it should be `require 'active_support/core_ext/enumerable'` without the `.rb` suffix, since that's added implicitly. – Per Lundberg Jul 06 '16 at 14:34
96

For Ruby >=2.4.0 you can use sum from Enumerables.

[1, 2, 3, 4].sum

It is dangerous to mokeypatch base classes. If you like danger and using an older version of Ruby, you could add #sum to the Array class:

class Array
  def sum
    inject(0) { |sum, x| sum + x }
  end
end
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
jrhicks
  • 14,759
  • 9
  • 42
  • 57
  • 19
    Monkeypatching base classes is not nice. – user3467349 Oct 04 '16 at 16:33
  • 2
    The point he is making is that you don't need to do the Monkey Patch for Ruby >= 2.4, and that monkey patching is dangerous, and that you can now sum enumerables natively, but there is also a way to backport the functionality. – Peter H. Boling Apr 05 '17 at 03:39
  • As noted, [Enumerable#sum](http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-sum) (and [Array#sum](http://ruby-doc.org/core-2.4.0/Array.html#method-i-sum)) were introduced in Ruby v2.4. Importantly, they do not have the same behavior as your `Array#sum`; namely, they take an optional argument and an optional block. Defining `Array#sum` as you have would result in an exception being raised wherever `sum` appeared in the code with an argument or block. – Cary Swoveland May 06 '18 at 23:57
  • If you need to supply a default value for when the `Array` is empty, like if you want to return a `Money` object instead of an `Integer`, you can do something like `array.sum( 0.to_money( "USD" ) )`. – Joshua Pinter Mar 05 '21 at 16:05
51

New for Ruby 2.4.0

You can use the aptly named method Enumerable#sum. It has a lot of advantages over inject(:+) but there are some important notes to read at the end as well.

Examples

Ranges

(1..100).sum
#=> 5050

Arrays

[1, 2, 4, 9, 2, 3].sum
#=> 21

[1.9, 6.3, 20.3, 49.2].sum
#=> 77.7

Important note

This method is not equivalent to #inject(:+). For example

%w(a b c).inject(:+)
#=> "abc"
%w(a b c).sum
#=> TypeError: String can't be coerced into Integer

Also,

(1..1000000000).sum
#=> 500000000500000000 (execution time: less than 1s)
(1..1000000000).inject(:+)
#=> 500000000500000000 (execution time: upwards of a minute)

See this answer for more information on why sum is like this.

Community
  • 1
  • 1
Eli Sadoff
  • 7,173
  • 6
  • 33
  • 61
  • If you need to supply a default value for when the `Array` is empty, like if you want to return a `Money` object instead of an `Integer`, you can do something like `array.sum( 0.to_money( "USD" ) )`. – Joshua Pinter Mar 05 '21 at 16:06
  • This answer is deprecated. In ruby2.6.6 for example, you **can** do `%w(a b c).sum # => "abc"` – Cadoiz Jul 28 '23 at 07:23
22

Ruby 2.4+ / Rails - array.sum i.e. [1, 2, 3].sum # => 6

Ruby pre 2.4 - array.inject(:+) or array.reduce(:+)

*Note: The #sum method is a new addition to 2.4 for enumerable so you will now be able to use array.sum in pure ruby, not just Rails.

typo
  • 1,041
  • 11
  • 15
19

Just for the sake of diversity, you can also do this if your array is not an array of numbers, but rather an array of objects that have properties that are numbers (e.g. amount):

array.inject(0){|sum,x| sum + x.amount}
HashFail
  • 536
  • 1
  • 5
  • 14
  • 3
    This is equivalent to doing: `array.map(&:amount).inject(0, :+)`. See other answers. – Richard Jones Feb 21 '14 at 03:49
  • 4
    In a way, yes. However, using `map` then `inject` requires you to loop through the array twice: once to create a new array, the other to sum the members. This method is slightly more verbose, but also more efficient. – HashFail Feb 21 '14 at 21:08
  • Apparently it is not more efficient, see https://gist.github.com/cameron-martin/b907ec43a9d8b9303bdc - credit to the comments in this answer: https://stackoverflow.com/a/1538949/1028679 – rmcsharry Jul 03 '19 at 15:53
  • 1
    @RichardJones or just `array.sum(&:amount)` as of today (using ruby >= 2.4 or rails). – Cadoiz Jul 28 '23 at 07:33
18

ruby 1.8.7 way is the following:

array.inject(0, &:+) 
Vova
  • 309
  • 3
  • 4
6

Ruby 2.4.0 is released, and it has an Enumerable#sum method. So you can do

array.sum

Examples from the docs:

{ 1 => 10, 2 => 20 }.sum {|k, v| k * v }  #=> 50
(1..10).sum                               #=> 55
(1..10).sum {|v| v * 2 }                  #=> 110
Santhosh
  • 28,097
  • 9
  • 82
  • 87
4

Also allows for [1,2].sum{|x| x * 2 } == 6:

# http://madeofcode.com/posts/74-ruby-core-extension-array-sum
class Array
  def sum(method = nil, &block)
    if block_given?
      raise ArgumentError, "You cannot pass a block and a method!" if method
      inject(0) { |sum, i| sum + yield(i) }
    elsif method
      inject(0) { |sum, i| sum + i.send(method) }
    else
      inject(0) { |sum, i| sum + i }
    end
  end
end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
grosser
  • 14,707
  • 7
  • 57
  • 61
4

for array with nil values we can do compact and then inject the sum ex-

a = [1,2,3,4,5,12,23.45,nil,23,nil]
puts a.compact.inject(:+)
thedudecodes
  • 1,479
  • 1
  • 16
  • 37
2

Method 1:

    [1] pry(main)> [1,2,3,4].sum
    => 10
    [2] pry(main)> [].sum
    => 0
    [3] pry(main)> [1,2,3,5,nil].sum
    TypeError: nil can't be coerced into Integer

Method 2:

   [24] pry(main)> [].inject(:+)
   => nil
   [25] pry(main)> [].inject(0, :+)
   => 0
   [4] pry(main)> [1,2,3,4,5].inject(0, :+)
   => 15
   [5] pry(main)> [1,2,3,4,nil].inject(0, :+)
   TypeError: nil can't be coerced into Integer
   from (pry):5:in `+'

Method 3:

   [6] pry(main)> [1,2,3].reduce(:+)
   => 6
   [9] pry(main)> [].reduce(:+)
   => nil
   [7] pry(main)> [1,2,nil].reduce(:+)
   TypeError: nil can't be coerced into Integer
   from (pry):7:in `+'

Method 4: When Array contains an nil and empty values, by default if you use any above functions reduce, sum, inject everything will through the

TypeError: nil can't be coerced into Integer

You can overcome this by,

   [16] pry(main)> sum = 0 
   => 0
   [17] pry(main)> [1,2,3,4,nil, ''].each{|a| sum+= a.to_i }
   => [1, 2, 3, 4, nil, ""]
   [18] pry(main)> sum
   => 10

Method 6: eval

Evaluates the Ruby expression(s) in string.

  [26] pry(main)> a = [1,3,4,5]
  => [1, 3, 4, 5]
  [27] pry(main)> eval a.join '+'
  => 13
  [30] pry(main)> a = [1,3,4,5, nil]
  => [1, 3, 4, 5, nil]
  [31] pry(main)> eval a.join '+'
  SyntaxError: (eval):1: syntax error, unexpected end-of-input
  1+3+4+5+
raj_acharya
  • 665
  • 5
  • 17
  • `reduce` and `inject` are literally the same thing. If you really want to build an object by yourself, you should consider [`each_with_object`](https://apidock.com/ruby/Enumerable/each_with_object) instead of `each` (Method 4). Suggestion: `[1,2,3,4,nil, ''].each_with_object(0) {|a| sum+= a.to_i }` – Cadoiz Jul 28 '23 at 07:31
1

If you feel golfy, you can do

eval [123,321,12389]*?+

This will create a string "123+321+12389" and then use function eval to do the sum. This is only for golfing purpose, you should not use it in proper code.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
-1

You can also do it in easy way

def sum(numbers)
  return 0 if numbers.length < 1
  result = 0
  numbers.each { |num| result += num }
  result
end
Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Prabhakar
  • 6,458
  • 2
  • 40
  • 51
  • 1
    This is very non-idiomatic Ruby, it looks like Ruby written by a C programmer. In Ruby, `inject` or `sum` are preferred. – user229044 Jul 31 '20 at 00:51
-10

You can use .map and .sum like:

array.map { |e| e }.sum
shabdar
  • 371
  • 1
  • 8