Ruby Array Inject

Need a simple explanation of the inject method

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.

Inject in array

In Ruby, result of last statement executed in a method is the return value.
If that last statement happens to be a conditional statement and does not execute, then return value is nil.

def meth
var = nil
10 if var # will not execute
end
p meth
#=> nil

Same is true for blocks as well.

In case of Array#inject, the return value of block given to inject becomes the new value of accumulator.

Since, in your case, the last statement of block does not execute due to condition being false (as options[:calculation] is nil), the accumulator value inadvertently becomes nil. To avoid it returning nil, you had to return the value of accumulator explicitly.

You could modify your code to something like below if you don't like to use array as explicit return statement.

aes = [1,2,3,4,3,4]
options = {}
test = aes.inject([]) do |array, value|
array + [value + 2] + (options[:calculation] ? [value - 1] : [])
end
#=> [3, 4, 5, 6, 5, 6]

How does array.inject(:+) work?

When the sole argument to inject is not a symbol, it is used as the initial value, otherwise, to_proc and & are applied to the argument, and that is used as a block. When there are two arguments, the first one is used as the initial value, and the second one must be a symbol, and would be used to create a block as described above.

A drawback of this is that you cannot use a symbol as the initial value of inject, but it is probably considered that there is no use case for that. I don't think this specification is clean.

Ruby inject to create array

inject works a little differently from how you're imagining. It simply returns the last return value of the loop as it loops through each item. An easy way to fix this is:

notebooks.inject([]) do |res, nb|
res << nb.guid if Recipe::NOTEBOOKS.include?(nb.name)
res # Returns the res array
end

That said, you should probably use select for your use case as you seem to be just filtering down which set of notebooks you want.. That is:

notebooks.select{|nb| Recipe::NOTEBOOKS.include?(nb.name)}.map(&:guid)

Generally, I've used inject when I need to run math on a group of items. e.g.

[1,2,3,4].inject(0) {|res, x| x * 2 + res}

Why is Array#inject called inject?

I prefer to think of inject as "injecting" an operation among the items inside the given array and returning the final result of the calculation.

(1..5).inject(:+)  #=> 15

In my example, it takes the number 1 to 5 and "injects" a sum operation among them, resulting in 1 + 2 + 3 + 4 + 5 = 15.

Also, it is aliased by reduce, as stated with details in https://ruby-doc.org/core-2.4.1/Enumerable.html#method-i-inject.

Using inject with an array of hashes

If you don't specify an argument to inject, the value for the memo object for the first iteration is the first element of the enumerable, an hash in this case. So you just have to pass 0 as the argument to inject:

array = [{lol: 1}, {lol: 2}]
array.inject(0) { |sum, h| sum + h[:lol] }
# => 3

Ruby Array Chainables Combining Map & Inject

If you use this wierd Symbol patch shown here:

https://stackoverflow.com/a/23711606/2981429

class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end

This allows you to pass arguments to the proc shorthand by means of Currying:

[[1,2],[3,4]].map(&:inject.(1, &:*))
# => [2, 12]

I'm sure this has been requested in Ruby core many times, unfortunately I don't have a link to the Ruby forums right now but I promise you it's on there.

Ruby Inject method problem

Assuming you need "for each separate created_at time, calculate total sum of all usd_conv up to this time", this is done by injecting values one-by-one into a data structure. Here's an example that "injects" records into two-value array:

  • the first item accumulates resultant records
  • the second accumulates the sum for a current moment

    array.inject([0,[]]) do |so_far,elem| 
    so_far[0] += elem.usd_conv
    so_far[1] << [elem.created_at, so_far[0]]
    so_far # Necessary because block should return the next so_far
    end

Using #inject to join strings from an array

Try this:

a.map{|t| t.capitalize}.join(" ")

I don't think you can escape from the extra space with inject. Also you need to do

memo = memo + word.capitalize + " " 

EDIT: as the statement has changed to force you not to use join and map, here is a bit ugly solution with inject:

a.inject("") do |memo, world|
memo << " " unless memo.empty?
memo << word.capitalize
end

Rewriting Ruby #inject (#reduce) using recursion?

An alternative implementation

class Array
def aggregate(accumulator = nil, &sumator)
return accumulator if empty?

drop(1).aggregate(accumulator ? sumator.(accumulator, first) : first, &sumator)
end
end

As for should you worry about mutating the original - in short - yes. Usually methods in Ruby don't mutate objects if possible and make a copy. There are often times bang (!) alternatives that do. Said "dangerous" methods mutate the original in place instead of returning a copy. However, in this situation this wouldn't make sense. The original is an array and the result is the sum.

As for mutating the original and returning the result separately, unless you have a real performance (or other) consideration, you shouldn't do it. It's unintuitive and can lead to confusing situations.



Related Topics



Leave a reply



Submit