Dennis Nedry
Dennis Nedry

Reputation: 4748

Deeper explanation of reduce / inject method in ruby

I've been racking my brains around this for some time.

When using reduce - why is the first element returned without executing the operation defined in the block? Or am I missing a crucial point in how reduce works?

In the following example:

arr = [1, 3, 5]

arr.reduce {|sum, n| sum + (n * 3) }
#=> 25

I would have expected the result to be 27.

Since:

0 + (1 * 3) = 3
3 + (3 * 3) = 12
12 + (5 * 3) = 27

After some time playing around with it I figured out that in the first "tick" - the object from the array just get's added to the sum instead of being multiplied. So that the calculation is more like:

??? = 1
1 + (3 * 3) = 10
10 + (5 * 3) = 25

Could someone help me figure out where I've gotten off the path?

Upvotes: 6

Views: 2642

Answers (3)

Rich Steinmetz
Rich Steinmetz

Reputation: 1291

I had a similar issue with the default values in Ruby inject/reduce methods, so I've tried to visualize it:

default values vizualized

Upvotes: 8

anquegi
anquegi

Reputation: 11522

I thnk that the explanation is in the help o¡f the methos in this case:

[1] pry(main)> cd Array
[2] pry(Array):1> ? reduce

From: enum.c (C Method):
Owner: Enumerable
Visibility: public
Signature: reduce(*arg1)
Number of lines: 33

Combines all elements of enum by applying a binary
operation, specified by a block or a symbol that names a
method or operator.

The inject and reduce methods are aliases. There
is no performance benefit to either.

If you specify a block, then for each element in enum
the block is passed an accumulator value (memo) and the element.
If you specify a symbol instead, then each element in the collection
will be passed to the named method of memo.
In either case, the result becomes the new value for memo.
At the end of the iteration, the final value of memo is the
return value for the method.

If you do not explicitly specify an initial value for memo,
then the first element of collection is used as the initial value
of memo.


   # Sum some numbers
   (5..10).reduce(:+)                             #=> 45
   # Same using a block and inject
   (5..10).inject { |sum, n| sum + n }            #=> 45
   # Multiply some numbers
   (5..10).reduce(1, :*)                          #=> 151200
   # Same using a block
   (5..10).inject(1) { |product, n| product * n } #=> 151200
   # find the longest word
   longest = %w{ cat sheep bear }.inject do |memo, word|
      memo.length > word.length ? memo : word
   end
   longest                                        #=> "sheep"

So you need to specify the first memo as 0, in your case is one:

[4] pry(Array):1> [1,3,5].reduce(0) {|sum, n| sum + (n * 3) }
=> 27

Upvotes: 2

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

It's in the docs.

If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo.

Upvotes: 7

Related Questions