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
Rails 404 Error for Stylesheet or JavaScript Files
Mini_Magick Gem Doesn't Work with My Imagemagick Install
How to Handle Combination []+= for Auto-Vivifying Hash in Ruby
Ruby Selenium Web Driver: How to Count Child Element Nodes of a Specific Node
Gmaps4Rails:Setting Map Width and Height
Ruby 2.4 on MAC Os Mojave:Image Not Found ... Related to Openssl.Bundle
Rescuing "Command Not Found" for Io::Popen
How to Refer to a Submodule's "Full Path" in Ruby
How to Fix Ruby Script Which Fails with Encoding Error: "\Xd8" on Us-Ascii
Rails: Allowing a Partial to Only Be Rendered Once
Elegant Way to Only Show Records If They Exist in Rails Erb
How to Get Multiple-Line User Input in Ruby
Bundler How to Uninstall Conflicting Dependency
Has_Many and No Method Error Issue
-Bash: /Usr/Local/Bin/Heroku: /Usr/Local/Bin/Ruby: Bad Interpreter: No Such File or Directory
How to Turn Off Bigint Primary Keys in Rails 5.1