A friend asked me recently why I enjoy programming in Ruby so much and I thought I’d share my thoughts here as well. We’ll focus on one piece of sample code, letting Ruby do most of the talking, that highlights some of my favorite features.
This example comes from some real code I was working on. We’ll refactor it step by step until we get something we’re proud of. Each time through, we’ll improve the code by using more and more of Ruby’s features.
Split an Array Into Keys and Values
Our goal is to write a split method that accepts an array and partitions it into two different arrays of keys and values. The minitest spec for split would look something like this.
The first attempt creates a couple arrays, loops through params, and adds the item to keys if its loop index is even or to values if it’s odd.
The neat thing about this is the Array#each iteration method that accepts a block of code (between the do/end keywords) and runs it once for each item in the array. Internal iterators are one of my favorite Ruby features.
We can tighten this up a bit by using multiple assignment to define the keys and values arrays on one line. And rather than define and increment the index variable ourselves, we’ll let the Array#each_with_index iterator do it for us. Now the outer scope isn’t cluttered with index variables that are only needed for the iterator block.
As a bonus, we used the Fixnum#even? method on the index, which reads a little easier than the modulo operator.
Array’s each and each_with_index methods are nice, but not as powerful as Array#partition. This iterator splits one array into two, based on the block you’ve given it. In our case, if the block returns true, the item goes into the keys array, false and it goes into values.
So Array#partition is convenient, but now we’re back to maintaining the index variable again. This still feels like too much bookkeeping, just to split an array.
It turns out that if you don’t pass a block of code to Array#partition, it returns an Enumerator object instead. Enumerators have a with_index method that will maintain the current loop index for us.
By combining two Enumerators together, we can partition an array based on item indexes in a single, readable, beautiful line of code.
What Do You Think?
Is there an easier way to split this array that I missed? What’s your favorite language feature, in Ruby or otherwise? Let me know in the comments!
- David Graham
16 Feb 2012