tags:

views:

191

answers:

6
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

I'm looking at this code but my brain is not registering how the number 10 can become the result. Would someone mind explaining what's happening here?

+9  A: 

You can think of the first block argument as an accumulator: the result of each run of the block is stored in the accumulator and then passed to the next execution of the block. In the case of the code shown above, you are defaulting the accumulator, result, to 0. Each run of the block adds the given number to the current total and then stores the result back into the accumulator. The next block call has this new value, adds to it, stores it again, and repeats.

At the end of the process, inject returns the accumulator, which in this case is the sum of all the values in the array, or 10.

Here's another simple example to create a hash from an array of objects, keyed by their string representation:

[1,"a",Object.new,:hi].inject({}) do |hash, item|
  hash[item.to_s] = item
  hash
end

In this case, we are defaulting our accumulator to an empty hash, then populating it each time the block executes. Notice we must return the hash as the last line of the block, because the result of the block will be stored back in the accumulator.

Drew Olson
Thanks for the great explanation and the hash example - that really made it clear.
+2  A: 

The code iterates over the four elements within the array and adds the previous result to the current element:

  • 1 + 2 = 3
  • 3 + 3 = 6
  • 6 + 4 = 10
John Topley
Nice simple illustration. Thanks.
+1  A: 

Inject applies the block

result + element

to each item in the array. For the next item ("element"), the value returned from the block is "result". The way you've called it (with a parameter), "result" starts with the value of that parameter. So the effect is adding the elements up.

Sam Hoice
+4  A: 

inject takes a value to start with (the 0 in your example), and a block, and it runs that block once for each element of the list.

  1. On the first iteration, it passes in the value you provided as the starting value, and the first element of the list, and it saves the value that your block returned (in this case result + element).
  2. It then runs the block again, passing in the result from the first iteration as the first argument, and the second element from the list as the second argument, again saving the result.
  3. It continues this way until it has consumed all elements of the list.

The easiest way to explain this may be to show how each step works, for your example; this is an imaginary set of steps showing how this result could be evaluated:

[1, 2, 3, 4].inject(0) { |result, element| result + element }
[2, 3, 4].inject(0 + 1) { |result, element| result + element }
[3, 4].inject((0 + 1) + 2) { |result, element| result + element }
[4].inject(((0 + 1) + 2) + 3) { |result, element| result + element }
[].inject((((0 + 1) + 2) + 3) + 4) { |result, element| result + element }
(((0 + 1) + 2) + 3) + 4
10
Brian Campbell
Thanks for writing out the steps. This helped a lot. Although I was a little confused about whether you mean that the diagram below is how the inject method is implemented underneath in terms of what's passed as arguments to inject.
The diagram below is based on how it *could* be implemented; it isn't necessarily implemented exactly this way. That's why I said it's an imaginary set of steps; it demonstrates the basic structure, but not the exact implementation.
Brian Campbell
+1  A: 

What they said, but note also that you do not always need to provide a "starting value":

[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

is the same as

[1, 2, 3, 4].inject { |result, element| result + element } # => 10

Try it, I'll wait.

When no argument is passed to inject, the first two elements are passed into the first iteration. In the example above, result is 1 and element is 2 the first time around, so one less call is made to the block.

Mike Woodhouse
A: 

This code doesn't allow the possibility of not passing a starting value, but may help explain what's going on.

def incomplete_inject(enumerable, result)
  enumerable.each do |item|
    result = yield(result, item)
  end
  result
end

incomplete_inject([1,2,3,4], 0) {|result, item| result + item} # => 10
Andrew Grimm