Is Inject the Same Thing as Reduce in Ruby

Is inject the same thing as reduce in ruby?

Yes, and it's also called fold in many other programming languages and in Mathematics. Ruby aliases a lot in order to be intuitive to programmers with different backgrounds. If you want to use #length on an Array, you can. If you want to use #size, that's fine too!

how does reduce or inject work in this code

Suppose we execute

{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)

so that within the method

default #=> 4
args #=> [:a, :b, :c]
self #=> { :a=>{:b=>{:c=>3 } } }

We then execute the following1:

args.empty? ? default : args.reduce(self) { |acum, key| acum.fetch(key) } rescue default
#=> [:a, :b, :c].empty? ? 4 : [:a, :b, :c].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
# acum.fetch(key) } rescue 4
#=> 3

If args #=> [:a, :b], we execute the following:

[:a, :b].empty? ? 4 : [:a, :b].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> {:c=>3}

If args #=> [:a, :b, :cat], then a KeyError exception is raised and the inline rescue returns the value of default:

[:a, :b, :cat].empty? ? 4 : [:a, :b, :cat].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> 4

and if args #=> [], [].empty? is true, so the value of default is again returned:

[].empty? ? 4 : [].reduce({ :a=>{:b=>{:c=>3 } } }) { |acum, key|
acum.fetch(key) } rescue 4
#=> 4

Fortunately, we no longer have to deal with such nonsense as we were given Hash#dig in Ruby 2.3.0, allowing us to write the following.

class Hash
def get_value( default, *keys )
keys.empty? ? default : dig(*keys) || default
end
end

{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :c)
#=> 3
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b)
#=> {:c=>3}
{ :a=>{:b=>{:c=>3 } } }.get_value(4, :a, :b, :cat)
#=> 4
{ :a=>{:b=>{:c=>3 } } }.get_value(4)
#=> 4

Note that the default receiver of dig is self.

1 Note that instead of ...args.reduce(self) { |acum, key| acum.fetch(key) } rescue default the author of that code could have written ...args.reduce(self) { |acum, key| acum.fetch(key, default) }. See Hash#fetch.

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

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.

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.

How to use reduce/inject in Ruby without getting Undefined variable


Block local variables

new_array doesn't exist outside the block of your reduce call. It's a "block local variable".

reduce does return an object, though, and you should use it inside your method.

sum = [1, 2, 3].reduce(0){ |acc, elem| acc + elem }
puts sum
# 6
puts acc
# undefined local variable or method `acc' for main:Object (NameError)

Your code

Here's the least amount of change for your method :

def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
new_array = str.split(//).reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end

return true if (new_array == new_array.sort)
end

Notes:

  • return isn't needed at the end.
  • true if ... isn't needed either
  • for loop should never be used in Ruby
  • reduce returns the result of the last expression inside the block. It was for in your code.
  • If you always need to return the same object in reduce, it might be a sign you could use each_with_object.
  • "test".split is just ["test"]

String and Enumerable have methods that could help you. Using them, you could write a much cleaner and more efficient method, as in @Phrogz answer.

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


Related Topics



Leave a reply



Submit