Compose a hash using inject method

Tags: ruby, hash, inject, enumerable
Publish Date: 2016-04-26

inject is a method defined in the Enumerable module, that most people would use to sum up values.
Here is a classic way people would use this method, whereby the argument 0 is used as a starting value:

[1,2,3,4,5,6,7,8,9,10].inject(0){ |memo, elem| memo + elem }
# => 55

Since inject is defined in Enumerable, it is also available to instances of Hash

Hash.included_modules
# => [Enumerable, Kernel]
{}.respond_to? :inject
# => true

Let's say we have 3 employees, their names and ages are represented this way:

employees = [{name: "Alan", age: 30}, 
             {name: "Tom", age: 45}, 
             {name: "Steve", age: 22} ]

Obviously we could sum up their ages using inject, but this is not necessarily useful

total_age = employees.inject(0) do |memo, elem|
  memo += elem[:age]
end
# => 97

However, inject is a lot more versatile than that. My former colleague Paul (archan937) likes to use inject as a way to compose a hash, a trick which I have adapted.

In our example, we could use the previous employees array to provide some much more meaningful information, such as the amount of years before each employee reaches his retirement age.

Assuming the retirement age is 65:

years_to_retirement = employees.inject({}) do |memo, elem|
  memo[elem[:name]] = 65 - elem[:age]
  memo
end
# => {"Alan"=>35, "Tom"=>20, "Steve"=>43} 

In the previous example, an empty hash ({}) was provided to inject as the initial value.
Then during each iteration, we added a key-value pair to this hash, using the employee's name as key and years-to-retirement as value.

 

Pay attention to the second block-parameter

One thing to bear in mind when iterating through a collection with inject (as with other iterators), is that iterating through an array works differently from iterating through a hash.
In the previous examples, we saw that the second block-parameter represents individual elements in the given array. (As expected)

To clarify:

employees.class
# => Array 
employees.inject(0) do |memo, elem|
  puts elem.inspect
end
# It prints:
# {:name=>"Alan", :age=>30}
# {:name=>"Tom", :age=>45}
# {:name=>"Steve", :age=>22}

But when iterating through key-value pairs within a hash, the second block-parameter would act differently.
Let's say, my wallet is represented by a hash and the banknotes' denominations (50, 20, 10 & 5) act as keys while the amounts of these banknotes act as the value:

my_wallet = {50 => 1, 20 => 2, 10 => 3, 5 => 2 }

# Now look what happens when we inspect the second block-parameter (called 'elem' in this example):

my_wallet.inject(0) do |memo, elem|
  puts elem.inspect
end
# It prints:
# [50, 1]
# [20, 2]
# [10, 3]
# [5, 2]

So we saw that inject turns the key-value pairs into arrays of two elements, then pass it on as the second block-parameter.
This behaviour is not unique to inject, but it also applies to other iterators such as map and each.

While we could sum up the amount of money in my_wallet by summing products of elem[0] * elem[1], a much clearer way would be to modify our second block-parameter this way:

my_wallet.inject(0) do |memo, (key,value)|
  memo +=  key * value
end
# => 130