In Progress
Unit 1, Lesson 1
In Progress

# Sum

Video transcript & code

Chances are, sooner or later you'll find yourself wanting to sum a list of numbers in Ruby. If you're using Rails, you'll simply use the `#sum` method added by `ActiveSupport`.

``````require "active_support/core_ext/enumerable"
data = [1,2,3,4,5,6,7,8,9,10]
data.sum                        # => 55
``````

As you can see from this code, if we just want to pull in the `Enumerable` extensions such as `#sum` from ActiveRecord, we can require `active_support/core_ext/enumerable`.

But what about using pure Ruby? If your introduction to Ruby was through Rails, you might have been surprised the first time you tried to use `#sum` outside of a Rails project, and found that it wasn't there.

``````data = [1,2,3,4,5,6,7,8,9,10]
data.sum                        # =>
# ~> -:2:in `<main>': undefined method `sum' for [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:Array (NoMethodError)
``````

Summing a list of numbers is fundamentally a reduction operation: we want to reduce the list into a single number which is derived from all of them put together. Ruby Enumerable objects give us a method for this class of operations, unsurprisingly called `#reduce`. You may also know it as `#inject`, which is its older, Smalltalk-inspired alias.

``````data.inject
``````

Fundamentally, `#reduce` iterates over the elements of the collection, combining one number with the next one, then combining that combination of two numbers with the next one, and so on. It takes a block which tells it how to combine one number with the next number in the list. The first argument the block receives is called the accumulator. It contains the cumulative result of all the previous combinations. The second block argument is the next number to be combined in.

Let's make this more concrete by using `#reduce` to sum up our list of numbers. Our block is simple: it takes the accumulator, and adds to it the next number. That's it. However, we need one more thing. `#reduce` combines each element with the ones before it, but when it first starts it needs something to combine with the very first element. We provide this "seed" value as an argument. In this case, we'll use `0`.

When we run this code, we can see it gives us the sum of our list.

``````data = [1,2,3,4,5,6,7,8,9,10]
sum = data.reduce(0) {|acc, n|
acc + n
}
sum                             # => 55
``````

Let's take a closer look at how this works by printing the accumulator and next number at each step of the operation.

``````data = [1,2,3,4,5,6,7,8,9,10]
sum = data.reduce(0) {|acc, n|
p [acc, n]
acc + n
}
sum                             # => 55
# >> [0, 1]
# >> [1, 2]
# >> [3, 3]
# >> [6, 4]
# >> [10, 5]
# >> [15, 6]
# >> [21, 7]
# >> [28, 8]
# >> [36, 9]
# >> [45, 10]
``````

On the first iteration, the seed value of `0` is combined with `1`, yielding `1`. On the second go-round, `1` is added to the next value of `2`, yielding `3`. Next time, that `3` is combined with `4`. And so on, until a cumulative `45` is combined with the last value of `10`, for a total of `55`.

Now that we understand how `#reduce` works, let's see if we can golf this code down a bit. In Ruby, the `+` operator is simply a method defined on numeric objects, like any other method. We can see this if we send it using method call notation. We can also explicitly send it using the `#public_send` method and the symbol `#+`.

``````2 + 2                           # => 4
2.+(2)                          # => 4
2.public_send(:+, 2)            # => 4
``````

Speaking of symbols, when a symbol is sent the `#to_proc` message, it returns an executable `Proc` object which will send that symbol as a message to the first argument of the `Proc`. That probably made no sense, so let's just do it. We assign the result of the `#to_proc` send to a variable named `add`. Then we invoke this proc by sending it the `#call` message, with two numbers as arguments. The result is the sum of the two numbers.

``````add = :+.to_proc               # => #<Proc:0x000000019697d0>
add.call(2, 2)                 # => 4
``````

The proc we call `add` here is more or less equivalent to a proc that takes two arguments, and sends the `#+` message to the first argument, passing the second as an argument.

``````add = :+.to_proc               # => #<Proc:0x00000001be0860>
add.call(2, 2)                 # => 4
myadd = ->(a,b) { a.public_send(:+, b) }
myadd.call(2,2)                # => 4
``````

Knowing this, we can remove the block from our reduction. In its place, we add a second argument. We'll start this argument with an ampersand, signaling to Ruby that it should convert the argument to a `Proc` and use it in place of a block passed to the method. Then we pass the `:+` symbol. Ruby will implicitly send this symbol the `#to_proc` message, and use the resulting `Proc` as the block. As we've just seen, the proc form of the `:+` symbol does exactly what we want: add the two numbers together.

``````data = [1,2,3,4,5,6,7,8,9,10]
sum = data.reduce(0, &:+)
sum                             # => 55
``````

But we don't stop here. As it happens, `#reduce` has special handling for symbols passed as arguments. We can remove the ampersand, making the symbol into an ordinary argument. The `#reduce` method, seeing that the argument is a symbol, will assume that we want to use it as if it were an ampersand block argument, and this code will continue to work as before.

``````data = [1,2,3,4,5,6,7,8,9,10]
sum = data.reduce(0, :+)
sum                             # => 55
``````

One more thing. Remember earlier when I said that we needed to supply a "seed" argument to get the accumulation started? I lied. If we leave the argument off, `#reduce` uses the first element as the seed, combines that element with the second, and so on.

``````data = [1,2,3,4,5,6,7,8,9,10]
sum = data.reduce(:+)
sum                             # => 55
``````

We have now arrived, via a roundabout journey, at the idiomatic way to sum up a list of numbers in Ruby. We can trivially modify this code to perform other operations on our listâ€”for instance, to XOR a list of numbers together, we could simply change the `+` to the numeric binary XOR operator:

``````data = [1,2,3,4,5,6,7,8,9,10]
checksum = data.reduce(:^)      # => 11
``````

Alright, that's enough for today. Happy hacking!