Map If in Ruby and an Introduction to Ruby’s Inject

by Ryan on July 25, 2011

When I first got into Ruby I kept hearing the saying: “There is more than one way to do it.” At first, this really freaked me out. I thought it meant that learning Ruby was going involving reading lots of code golf samples where developers come up with tricky, cleaver, and mind boggling ways to accomplish small problems. That original thought could not have been more false. Over the past year I’ve discovered that Ruby’s “There is more than one way to do it” really means that the best way to do something is generally the most readable and understandable way.

Map if

Here is a little example of multiple ways to do the same thing, what I like to call the “Map if” problem.

Map if involves taking an array (or any enumerable object) and returning a manipulated version of every element for which a certain condition is met. So, lets write a piece of code that takes an array of integers and returns an array of those integers multiplied by two if the integer is an even number.

given [1, 2, 3, 4, 5, 6] return [2*2, 4*2, 6*2] or [4, 8, 12]

Select/Map

A very straight forward way to solve this problem is just chaining two array methods together.

[1, 2, 3, 4, 5, 6].select { |elm| elm % 2 == 0 }.map { |elm| elm * 2 }
# => [4, 8, 12]

First select will filter the array but the condition defined in the block. Then map will apply its block onto each element in the array that select built.

Inject

A better way is to solve this problem is to use Enumerable’s inject, which is somewhat foreign to new ruby programmers. It’s syntax is a little off putting at first, it takes 2 parameters: a starting object and a block. The block then takes another 2 parameters: an element and the result. Before I explain this method any further here is an the above map/if done with inject.

[1, 2, 3, 4, 5, 6].inject([]) do |result, elm|
  result << elm * 2 if elm % 2 == 0
  result
end
# => [4, 8, 12]

So what is this doing? Inject in english is:

Start with some object (our empty array) and then pass each element in our caller ([1, 2, 3, 4, 5, 6]) to the block. Provide a result object that the block can freely change. The result starts out as our first parameter (the empty array) and then becomes whatever the block evaluates last.

So why is inject better? There are a number of reasons

One method with one block
Our select/map example above was straight forward, but it involves multiple method calls and multiple blocks. The longer our method chain becomes the less readable our code is. Inject is readable because the block is just a block, that is to say, if you write readable Ruby in the block then the inject method is going to be very easy to follow.

Part of Enumerable
Any object that extends enumerable is going to have inject. This makes it very reusable. I find myself using inject on a daily bases for all sorts of different use cases.

Powerful
The real power of inject lies in it’s ability to build new objects while looking at every element in the caller individually. Since inject can draw values from any enumerable object it makes it an ideal method for many common uses such as filtering, grouping, summing and everything else related to building. The map if example showed how inject can be used to filter and build.

Grouping

Grouping a list of words by the first letter of the word:

["alpha", "bravo", "charlie", "bark", "almond"].inject({}) do |result, elm|
  result[elm[0].to_sym] ||= []
  result[elm[0].to_sym] << elm
end
# => {:a=>["alpha", "almond"], :b=>["bravo", "bark"], :c=>["charlie"]}

Summing

Inject is used in many common ruby idioms. The next code line will look like absolute garbage to anyone not familiar with Ruby. However, to someone that knows inject it is a very clear and readable code.

Sum up all the numbers between 1 and 10 inclusive.

(1..10).inject(:+)
# => 55

Short and simple, but what happened to our parameters and block?

No Starting Value
When you call inject without a starting value (the first parameter) the starting value will default to the first element inside your enumerable. So in the above example result would be 1 for the first pass.

No Block
In Ruby 1.9 all symbols have a to_proc method. Instead of passing a block where a block is required, you can pass a symbol and that symbol will create a proc that then calls itself on the object. The object in this case is our result. So :+ will turn into result + *args, where *args is our element.

One Response leave one →
  1. Charlie permalink
    December 23, 2011

    The code block:

    [1, 2, 3, 4, 5, 6].inject([]) do |result, elm|
    result << elm * 2 if elm % 2 == 0
    end

    returns a undefined method `<<' for nil:NilClass

    Another way of writing this could be:

    [1, 2, 3, 4, 5, 6].inject([]) do |result, elm|
    result << elm * 2 if elm % 2 == 0
    result
    end

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS