Need a simple explanation of the inject method
Asked Answered
W

18

152
[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?

Wattle answered 2/4, 2009 at 16:28 Comment(3)
See Wikipedia: Fold (higher-order function): inject is a "fold left", although (unfortunately) often with side-effects in Ruby usage.Babbittry
in case you'd like to digest a less mathematical visualization than the wikipedia article from this answer below.Blancheblanchette
I prefer the alias for #inject, #reduce because how these operations work. When you use #map the general way, you get the same number of elements that you "put" into #map, you get a one-to-one mapping. With #reduce you get less - usually 1 - element as a result, thus then name reduce. Of course, this is Ruby, you can tweak both #map and #reduce to behave differently.Ikhnaton
T
218

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.

Tetroxide answered 2/4, 2009 at 16:31 Comment(2)
great explanation, however, in the example given by the OP, what is being returned (like hash is in your example). It ends with result + explanation and should have a return value, yes?Addlebrained
@Addlebrained the result + explanation is both the transformation to the accumulator and the return value. It's the last line in the block making it an implicit return.Divers
D
90

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
Dramatics answered 2/4, 2009 at 16:39 Comment(3)
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.Wattle
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.Dramatics
This was very helpful, I've tried to visualize this in my answer below.Blancheblanchette
F
35

The syntax for the inject method is as follows:

inject (value_initial) { |result_memo, object| block }

Let's solve the above example i.e.

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

which gives the 10 as the output.

So, before starting let's see what are the values stored in each variables:

result = 0 The zero came from inject(value) which is 0

element = 1 It is first element of the array.

Okey!!! So, let's start understanding the above example

Step :1 [1, 2, 3, 4].inject(0) { |0, 1| 0 + 1 }

Step :2 [1, 2, 3, 4].inject(0) { |1, 2| 1 + 2 }

Step :3 [1, 2, 3, 4].inject(0) { |3, 3| 3 + 3 }

Step :4 [1, 2, 3, 4].inject(0) { |6, 4| 6 + 4 }

Step :5 [1, 2, 3, 4].inject(0) { |10, Now no elements left in the array, so it'll return 10 from this step| }

Here Bold-Italic values are elements fetch from array and the simply Bold values are the resultant values.

I hope that you understand the working of the #inject method of the #ruby.

Finality answered 13/7, 2016 at 12:4 Comment(0)
P
19

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
Pevzner answered 2/4, 2009 at 16:32 Comment(0)
B
15

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.

Britannic answered 2/4, 2009 at 19:9 Comment(0)
V
14

The number you put inside your () of inject represents a starting place, it could be 0 or 1000. Inside the pipes you have two place holders |x, y|. x = what ever number you had inside the .inject('x'), and the secound represents each iteration of your object.

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

1 + 5 = 6 2 + 6 = 8 3 + 8 = 11 11 + 4 = 15

Viyella answered 9/4, 2014 at 19:18 Comment(0)
W
6

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.

Winne answered 2/4, 2009 at 16:33 Comment(0)
D
6

tldr; inject differs from map in one important way: inject returns the value of the last execution of the block whereas map returns the array it iterated over.

More than that the value of each block execution passed into the next execution via the first parameter (result in this case) and you can initialize that value (the (0) part).

Your above example could be written using map like this:

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

Same effect but inject is more concise here.

You'll often find an assignment happens in the map block, whereas an evaluation happens in the inject block.

Which method you choose depends on the scope you want for result. When to not use it would be something like this:

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

You might be like all, "Lookie me, I just combined that all into one line," but you also temporarily allocated memory for x as a scratch variable that wasn't necessary since you already had result to work with.

Dealings answered 21/3, 2015 at 17:52 Comment(0)
M
5

This is a simple and fairly easy to understand explanation:

Forget about the "initial value" as it is somewhat confusing at the beginning.

> [1,2,3,4].inject{|a,b| a+b}
=> 10

You can understand the above as: I am injecting an "adding machine" in between 1,2,3,4. Meaning, it is 1 ♫ 2 ♫ 3 ♫ 4 and ♫ is an adding machine, so it is the same as 1 + 2 + 3 + 4, and it is 10.

You can actually inject a + in between them:

> [1,2,3,4].inject(:+)
=> 10

and it is like, inject a + in between 1,2,3,4, making it 1 + 2 + 3 + 4 and it is 10. The :+ is Ruby's way of specifying + in the form of a symbol.

This is quite easy to understand and intuitive. And if you want to analyze how it works step by step, it is like: taking 1 and 2, and now add them, and when you have a result, store it first (which is 3), and now, next is the stored value 3 and the array element 3 going through the a + b process, which is 6, and now store this value, and now 6 and 4 go through the a + b process, and is 10. You are essentially doing

((1 + 2) + 3) + 4

and is 10. The "initial value" 0 is just a "base" to begin with. In many cases, you don't need it. Imagine if you need 1 * 2 * 3 * 4 and it is

[1,2,3,4].inject(:*)
=> 24

and it is done. You don't need an "initial value" of 1 to multiply the whole thing with 1. This time, it is doing

(((1 * 2) * 3) * 4)

and you get the same result as

1 * 2 * 3 * 4
Misdate answered 18/8, 2019 at 6:7 Comment(0)
A
4
[1, 2, 3, 4].inject(0) { |result, element| result + element } # => 10

is equivalent to the following:

def my_function(r, e)
  r+e
end

a = [1, 2, 3, 4]
result = 0

a.each do |value|
  result = my_function(result, value)
end
Adamik answered 9/1, 2015 at 0:59 Comment(0)
G
3

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

In plain English, you are going through (iterating) through this array ([1,2,3,4]). You will iterate through this array 4 times, because there are 4 elements (1, 2, 3, and 4). The inject method has 1 argument (the number 0), and you will add that argument to the 1st element (0 + 1. This equals 1). 1 is saved in the "result". Then you add that result (which is 1) to the next element (1 + 2. This is 3). This will now be saved as the result. Keep going: 3 + 3 equals 6. And finally, 6 + 4 equals 10.

Gammadion answered 2/3, 2016 at 16:4 Comment(0)
B
3

One day, I was also banging my head against the default values in Ruby inject/reduce methods, so I've tried to visualize my issue:

default values vizualized

Blancheblanchette answered 15/2, 2021 at 19:21 Comment(2)
i really love your way to explain this. I found myself trying to understand that the last week and I realized this same behavior printing each iteration and write it on a papersheet. :)Morph
@WilliamRomero that’s pretty cool, happy it helped! I often have this kind of stuff in my mind and I think our industry lacks more visuals:))Blancheblanchette
A
2

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
Alie answered 2/4, 2009 at 23:47 Comment(0)
O
1

Start here and then review all methods that take blocks. http://ruby-doc.org/core-2.3.3/Enumerable.html#method-i-inject

Is it the block that confuses you or why you have a value in the method? Good question though. What is the operator method there?

result.+

What does it start out as?

#inject(0)

Can we do this?

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

Does this work?

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

You see I'm building on to the idea that it simply sums all the elements of the array and yields a number in the memo you see in the docs.

You can always do this

 [1, 2, 3, 4].each { |element| p element }

to see the enumerable of the array get iterated through. That's the basic idea.

It's just that inject or reduce give you a memo or an accumulator that gets sent out.

We could try to get a result

[1, 2, 3, 4].each { |result = 0, element| result + element }

but nothing comes back so this just acts the same as before

[1, 2, 3, 4].each { |result = 0, element| p result + element }

in the element inspector block.

Outgrow answered 19/11, 2016 at 18:39 Comment(0)
D
0

There is another form of .inject() method That is very helpful [4,5].inject(&:+) That will add up all the element of the area

Deas answered 31/3, 2017 at 16:17 Comment(0)
H
0

A common scenario that arises when using a collection of any sort, is to get perform a single type of operation with all the elements and collect the result.

For example, a sum(array) function might wish to add all the elements passed as the array and return the result.

A generalized abstraction of same functionality is provided in Ruby in the name of reduce (inject is an alias). That is, these methods iterate over a collection and accumulate the value of an operation on elements in a base value using an operator and return that base value in the end.

Let's take an example for better understanding.

>>> (5..10).inject(1) {|product, n| product * n }
=> 151200

In above example, we have the following elements: a base value 1, an enumerable (5..10), and a block with expressions instructing how to add the calculated value to base value (i.e., multiply the array element to product, where product is initialized with base value)

So the execution follows something like this:

# loop 1
n = 1
product = 1
return value = 1*1

# loop 2
n = 2
product = 1
return value = 1*2

n = 3
product = 2
return value = 2*3

.. As you can notice, the base value is continually updated as the expression loops through the element of container, thus returning the final value of base value as result.

Heterodox answered 3/2, 2022 at 8:52 Comment(0)
B
0

In Ruby, the #inject method is used to accumulate values within an enumerable (e.g., an array) by applying a binary operation specified by a block. It is also known as #reduce. The #inject method takes an initial value (accumulator) and a block that defines how each element of the enumerable should be combined with the current accumulator.

Here's a basic example to illustrate how #inject works:

# Example 1: Summing an array of numbers
numbers = [1, 2, 3, 4, 5]

# Using inject to calculate the sum
sum = numbers.inject(0) do |accumulator, number|
  accumulator + number
end

puts "Sum: #{sum}" # Output: Sum: 15

In this example:

The inject(0) part specifies that the initial value of the accumulator is 0. The block { |accumulator, number| accumulator + number } defines how each element in the array is combined with the accumulator. It adds each number to the accumulator. Now, let's break down the iterations step by step for this example:

Iteration 1:

Initial accumulator value: 0
Current element: 1
Block operation: 0 + 1
Updated accumulator: 1

Iteration 2:

Initial accumulator value: 1 (from the previous iteration)
Current element: 2
Block operation: 1 + 2
Updated accumulator: 3

Iteration 3:

Initial accumulator value: 3 (from the previous iteration)
Current element: 3
Block operation: 3 + 3
Updated accumulator: 6

Iteration 4:

Initial accumulator value: 6 (from the previous iteration)
Current element: 4
Block operation: 6 + 4
Updated accumulator: 10

Iteration 5:

Initial accumulator value: 10 (from the previous iteration)
Current element: 5
Block operation: 10 + 5
Updated accumulator: 15

After iterating through all elements in the array, the final accumulator value is 15, which represents the sum of all numbers in the array.

This is just a simple example, and #inject can be used for various purposes, such as calculating the product, finding the maximum or minimum, and more. The key is to provide a block that defines how the elements should be combined with the accumulator.

Byelection answered 21/11, 2023 at 5:22 Comment(0)
S
-1

Is the same as this:

[1,2,3,4].inject(:+)
=> 10
Stamp answered 17/10, 2014 at 0:56 Comment(1)
While this is factual, it doesn't answer the question.Uriel

© 2022 - 2024 — McMap. All rights reserved.