Need a Simple Explanation of the Inject Method

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.

About the inject method: How does it work?

inject takes an optional start value, and a block taking an intermediate value and element and returning a new intermediate value.

So:

What does (0) stand for?

The start value parameter to inject.

What does the command "do" do?

It is not a command; it marks the start of the block (terminated by end). .inject(0) do ... end is almost (except for some syntactic issues) the same as .inject(0) { ... }. Usually, do ... end is used for multi-line blocks and { ... } for single-line blocks, but it is not a rule.

What does |res, e| mean?

Those are the block parameters (intermediate value and current element), here probably called after "result" and "element", respectively.

Let's see on a simplified example: (1..3).inject(0) do |res, e| res + e end will set the intermediate result to 0. Then it will pass this intermediate result and the first element of the enumerable being injected: res is 0 and e is 1. The value of the block is the value of its last expression, which is 1 (result of 0 + 1). This 1 now becomes the new intermediate value, and 2 becomes the next current element. The value of the block is 3 (result of 1 + 2). In the next iteration, intermediate value is 3, and the current element also 3, resulting in 6 (3 + 3). The range will stop yielding elements now that we reached its upper boundary, and inject returns with the last intermediate result calculated, 6.

Also, the last question am I right to assume that "p" at the end just stands for puts or print?

Almost. p is its own beast. p x is roughly synonymous with puts x.inspect; x - i.e. it prints the value in a bit different format, and unlike puts which always returns nil, p returns the value unchanged. Thus, p res at the end of your block will not destroy the code by making it return nil, but transparently return res.

I need explanation of method inject

djeca.order(name: :asc).inject([]) { |sum, c| sum += c.mysort}.uniq

is equivalent to

sum = []
djeca.order(name: :asc).each{|c| sum = sum + c.mysort}
sum.uniq

Adding arrays is actually concatening, so your code just appends all the c.mysort into an array.

If I understand it correctly, you could also write :

djeca.order(name: :asc).map{|c| c.mysort}.flatten.uniq

Depending on your audience, you might want to write one or the other.
Note that you don't need to assign a value to sum in the inject block, it is done automatically.

(1..10).inject(0){|mem,i| mem += i}
#=> 55

(1..10).inject(0){|mem,i| mem + i}
#=> 55

How does the inject method work?

As specified in the doc: https://apidock.com/ruby/Enumerable/inject

inject:

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

You can use it with enumerable(array, range, ..) like this,

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

or short-hand style,

[1, 2, 3].inject(&:+)

If you're wondering about this (&:+) and how it works, check this also,

What do you call the &: operator in Ruby?

Can someone explain a real-world, plain-language use for inject in Ruby?

Here are a couple of inject() examples in action:

[1, 2, 3, 4].inject(0) {|memo, num| memo += num; memo} # sums all elements in array

The example iterates over every element of the [1, 2, 3, 4] array and adds the elements to the memo variable (memo is commonly used as the block variable name). This example explicitly returns memo after every iteration, but the return can also be implicit.

[1, 2, 3, 4].inject(0) {|memo, num| memo += num} # also works

inject() is conceptually similar to the following explicit code:

result = 0
[1, 2, 3, 4].each {|num| result += num}
result # result is now 10

inject() is also useful to create arrays and hashes. Here is how to use inject() to convert [['dogs', 4], ['cats', 3], ['dogs', 7]] to {'dogs' => 11, 'cats' => 3}.

[['dogs', 4], ['cats', 3], ['dogs', 7]].inject({'dogs' => 0, 'cats' => 0}) do |memo, (animal, num)|
memo[animal] = num
memo
end

Here is a more generalized and elegant solution:

[['dogs', 4], ['cats', 3], ['dogs', 7]].inject(Hash.new(0)) do |memo, (animal, num)|
memo[animal] = num
memo
end

Again, inject() is conceptually similar to the following code:

result = Hash.new(0)
[['dogs', 4], ['cats', 3], ['dogs', 7]].each do |animal, num|
result[animal] = num
end
result # now equals {'dogs' => 11, 'cats' => 3}

Deeper explanation of reduce / inject method in ruby

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

default values vizualized

Why do I need to use .inject(0) rather than .inject to make this work?

What we can read in API:

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

So item_numbers[0] will be specified as an initial value - but it is not a number, it is an object. So we have got an error

undefined method `+'.

So we have to specify initial value as 0

item_numbers.inject(0){ |sum, i| sum + i }

custom ruby inject method not producing expected output

I was able to solve it with code.

 def my_inject(initial = nil, second = nil)
arr = is_a?(Array) ? self : to_a
sym = initial if initial.is_a?(Symbol) || initial.is_a?(String)
acc = initial if initial.is_a? Integer

if initial.is_a?(Integer)
sym = second if second.is_a?(Symbol) || second.is_a?(String)
end

if sym
arr.my_each { |x| acc = acc ? acc.send(sym, x) : x }
elsif block_given?
arr.my_each { |x| acc = acc ? yield(acc, x) : x }
end
acc
end
alias my_reduce my_injec

How to create a custom implementation of inject method?

If you debug Ruby's own implementation you will see that it starts the iteration from the second element in case no default memo is given:

> [1,2,3].inject { |m, i| puts "M: #{m} | I: #{i}"; break }
M: 1 | I: 2

Therefore your implementation should look like this:

def my_inject(*args)
init = args.size > 0
memo = init ? args[0] : self[0]

self.drop(init ? 0 : 1).my_each do |item|
memo = yield(memo, item)
end

return memo
end

puts [1,2,3,4].my_inject(0) { |memo, i| memo+i} #=> 10
puts [1,2,3,4].my_inject { |memo, i| memo+i} #=> 10

The reason for init = args.size > 0 is you have to take into account that someone might want to do [...].my_inject(false) { ... }. Therefore you cannot check if memo == false to determine if you need to skip the first element or not, you have to check the actual argument count that was given.

Where can I get a simple explanation of policy injection?

The MSDN documentation for Policy Injection has a pretty clear explanation:

Applications include a mix of business
logic and crosscutting concerns, and
the two are typically
intermingled—which can make the code
harder to read and maintain. Each task
or feature of an application is
referred to as a "concern." Concerns
that implement the features of an
object within the application, such as
the business logic, are core concerns.
Crosscutting concerns are the
necessary tasks, features, or
processes that are common across
different objects—for example,
logging, authorization, validation,
and instrumentation. The purpose of
the Policy Injection Application Block
is to separate the core concerns and
crosscutting concerns.

Simply put, the PI block lets developers define a set of policies that specify the behavior of objects in the system. So your core business logic, such as the code that calculates profit per unit in a fiscal year (one concern), is separated from the logging of that execution of logic (another, but more often used, concern).

The same documentation says that the PI block is not AOP because:

  • It uses interception to enable only pre-processing handlers and post-processing handlers.
  • It does not insert code into methods.
  • It does not provide interception for class constructors.

So trying to look at PI from an AOP perspective can muddy the waters a bit.



Related Topics



Leave a reply



Submit