February 5, 2008 — brian

Enumerable#inject, that is.

I know that there are a lot of Budding Rubyists out there. Are you a former (or current) Java developer? That was me, but I saw the light and have moved on to greener pastures.

A big step in the transition from Java to Ruby was the adoption of inject as a first-class weapon in my iteration arsenal. So, what’s the big deal, and how does it work? Let’s break it down.

You’re going to use inject to iterate through some values and use each of them to manipulate some other object. Let’s say that we have an array of two-element arrays (a la [[‘a’, 1], [‘b’, 2], [‘c’, 3]]) and want to construct a hash with the first element as the key and the second element as the value ({ ‘a’ => 1, ‘b’ => 2, ‘c’ => 3}). Someone just transitioning to Ruby might do this:

hash = {} array . each do | current | hash [ current [ 0 ]] = current [ 1 ] end

You can do this with inject like so:

hash = array . inject ( {} ) do | results , current | results [ current [ 0 ]] = current [ 1 ] results end

Ok, to be fair…this code is not simpler. Not as pretty, imho. More Ruby-esque? Sure. Patience, Grasshopper.

You can think of inject as a replacement for each that takes an argument representing the starting state of the object you want to manipulate. We want to construct a hash, so this starting state is {} . The block, then, receives another argument (I call it ‘results’ above): the value of your object passed in from the previous iteration.

One key thing to remember when using inject : you must return the ‘results’ from the iteration at the end of the block. Not doing so is a very common mistake that will totally break you.

Let’s pick a different example: we want to sum the values in an array. Old way:

total = 0 array . each do | current | total += current end total

With inject :

total = array . inject ( 0 ) do | total , current | total + current end

Again, you mentally translate the above to:

– start with 0

– iterate through each value, adding the current value to the total, and

– pass the total on to the next iteration

Easy peasy. (What, you just want to use ActiveSupport’s Enumerable#sum ? Bleh. Fair enough.) Ok, one more. This one actually shows it used in a realistic scenario.

Array . class_eval do def hash_by ( attribute ) inject ( {} ) do | results , obj | results [ obj . send ( attribute ) ] = obj results end end end

Here, I have reopened the Array class to add a new method, hash_by(). I use it to construct a cache of objects so that I can quickly look up a specific object using a unique object attribute. Here it is in action:

class User attr_accessor : name , : age , : email def initialize ( name , age , email ) @ name = name @ age = age @ email = email end end users = [ User . new ( ' Brian ' , 32 , ' brian@foo.bar.com ' ) , User . new ( ' Jim ' , 46 , ' jim@foo.bar.com ' ) , User . new ( ' Scott ' , 33 , ' scott@foo.bar.com ' ) , User . new ( ' Kenton ' , 32 , ' kenton@foo.bar.com ' ) , User . new ( ' Chris ' , 34 , ' chris@foo.bar.com ' ) ] user_cache = users . hash_by ( : name ) puts user_cache [ ' Brian ' ] . inspect puts user_cache [ ' Scott ' ] . inspect

With the constructed User cache, we can now get to the object of our choice in constant time. Yay, inject !