← Back to Upcase

Ruby inject method


(Scott Hollinshead) #1

I have been working through the rubymonk tutorials and come across this method

def add(*numbers)
  numbers.inject(0) {|sum, number| sum + number }
end

I have looked on Ruby Docs but I still can’t figure out what the inject method is doing.

Could someone explain this in simple terms please?

Thanks


(George Brocklehurst) #2

Hi Scott,

Inject (also known as reduce) is a way of taking a list of values and boiling them down to a single result. In the example you posted it’s taking a list of numbers and reducing them down to their sum.

The value you pass to inject (in your example, this is 0) is the initial result. This gets passed into the block (sum in your example) with the first item in the array (number in your example), and the value returned by the block becomes the new result (sum + number). That new result is then passed into the block with the second value in the array, and so on.

Here’s an example:

[1, 2, 3].inject(0) { |sum, number| sum + number }

Let’s unroll the loop and see what would happen if we wrote this out without using inject:

result = 0
block = Proc.new { |sum, number| sum + number }

[1, 2, 3].each do |number|
  result = block.call(result, number)
end

result # => 6

If we get rid of each as well, it might be even more clear:

result = 0
block = Proc.new { |sum, number| sum + number }

result = block.call(result, 1) # => 1
result = block.call(result, 2) # => 3
result = block.call(result, 3) # => 6

result # => 6

Hope that helps!


(Mike Burns) #3

We have a long description of it here: http://robots.thoughtbot.com/post/17782192029/derive-inject-for-a-better-understanding

To re-phrase: Enumerable#inject takes two arguments: a base case and a block. Each item of the Enumerable is passed to the block, and the result of the block is fed into the block again next time.

Walk through it:

[].inject(1) {|accumulator, item| accumulator + item }

The base case (1) is the result on the empty list. The above produces 1.

[2].inject(1) { |accumulator, item| accumulator + item }

This Enumerable has one element, 2, so the block is run with the base case (1) and the element (2):

lambda { |accumulator, item| accumulator + item }.call(1, 2)

And the result is 3.

[2,4].inject(1) { |accumulator, item| accumulator + item }

FIrst the block is called with 1 and 2, as shown above, producing 3. This 3 is then fed back into the block, along with 4:

lambda { |accumulator, item| accumulator + item }.call(3, 4)

This produces 7, which is the final result.

In the blog post linked above I give another way to think about it: recursively. Picture what an empty list class would look like:

class EmptyList
  def inject(base, &block)
    base
  end
end

With no items in the list, it just produces the base case. On the other hand, imagine a class that represents the first item of the list and the rest of the list (for example, given [2,3,5,7,11], this object would be RecusiveList.new(2, [3,5,7,11])):

class RecursiveList
  def initialize(first, rest)
    @first = first
    @rest = rest
  end

  def inject(accumulator, &block)
    new_accumulator = block.call(accumulator, @first)
    @rest.inject(new_accumulator, &block)
  end
end

In this case the first thing #inject does is calculate the next accumulator by calling the block, passing in the prior accumulator in addition to the first value this object knows about. Then, since the only other thing this object has is an Enumerable, it calls #inject on that Enumerable, giving this new accumulator as the base case.

Try walking through this example of the above to see how it functions:

list = RecusiveList.new(4, EmptyList.new)
list.inject(2) { |accumulator, item| accumulator * item }

Another way to think about it is in a more imperative style. This is how it’s implemented in the MRI Ruby code (in C).

class List
  def initialize(enumerable)
    @enumerable = enumerable
  end

  def inject(base, &block)
    accumulator = base

    @enumerable.each do |item|
      accumulator = block.call(accumulator, item)
    end

    return accumulator
  end
end

Again stepping through an example will be instructive. Try:

list = List.new([4, 9])
list.inject(2) { |accumulator, item| accumulator * item }

(Scott Hollinshead) #4

Thank you @georgebrock and @mikeburns for clearing that up for me, it makes a lot more sence to me now :smile:


(Geoff Harcourt) #5

The anti-pattern that #inject has most helped me remove is having to create an empty variable populated with a nil or empty collection (hash or array) and then to populate it with the results of #each. If you ever find yourself doing something like this:

result = 0
scores.each do |score|
result += score
end

do_something_with_result(result)

then you’re in a great situation to use #inject!


(Pedro Moreira) #6

While trying to understand inject and collect, I came across a video that was very helpful. Actually, it’s like @georgebrock and @mikeburns explanation animated.