Life beyond the each iterator

Today I will take a break from the IM integration with XMPP4r series and write about something completely different. Writing about XMPP4r just for the heck of it would become boring for everyone quite fast.
Let me dim the light, start some sentimental music, light some candles, make an exaggerated smile so I can become intimate with you and talk about the past.
More than 1 year ago… I was so excited. I was learning Ruby and, most precisely, I was learning blocks and block-based iterators. Once I grasped the concept, I made the mistake of using almost exclusively the each iterator in every situations.
each is like your oldest pair of slippers
Old slippers are comfortable and comforting. You put them on without shame, ignoring the laughs as well as the cruel remarks coming from your “friends”. The each iterator is also a comfortable pair of slippers. If you came to ruby from another language, it probably turned you on right away. Chances are that you said to yourself : “Damn, I will put my feet into that!”
There is no problem with the each iterator, it is simple, easy to use and easy to read. The problem is when you use it in every situations. When you do something like in the following example, you suffer from the syndrome of the comforting slippers.

@dogs = Dog.find(:all)
@dogs_to_keep = []
@dogs.each { |dog| @dogs_to_keep.push(dog) if calculate_emotional_intelligence(dog) >= 50 }

Here the programmer refused to remove his old slippers because he refused to see the life beyond the each iterator. A class like Array proposes other iterators that would have simplify the above code greatly.
This method is exactly what our programmer needed. Like each, it iterates over all elements in the array… but it does more. At each passage in the block, and if the value returned by the block is true, the corresponding element will be pushed into a new array.

@dogs = Dog.find(:all)
@dogs_to_keep = {|dog| calculate_emotional_intelligence(dog) >= 50}

You could also do it the other way around, rejecting dogs with little emotional intelligence.

@dogs = Dog.find(:all)
@dogs_to_keep = @dogs.reject {|dog| calculate_emotional_intelligence(dog) < 50}

Or you could modify the original array directly with reject!

@dogs = Dog.find(:all)
@dogs.reject! {|dog| calculate_emotional_intelligence(dog) < 50}

collect and map (same thing)
Finally, collect and map can be used when you want to create a new array based on the values returned at every passage in the block.

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary
  @fear_factors = @dogs.collect {|dog| dog[:fear_factor] + 5}
  #Output : @fear_factors contains [8,12]

You can also modify the original array with collect! or map!

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary! {|dog| dog[:fear_factor] + 5}
  #Output : @dogs contains [8,12]

Note how the resulting array contains 2 Fixnum items AND NOT 2 hashes. As you can see, the value returned by the block BECOMES an element in the new array. If you want to operate on the array directly, use the each slippers, that way :

@dogs = [{:name => "Benji", :fear_factor => 3},{:name => "Rex", :fear_factor => 7}]
if outside_atmosphere == :dark_and_scary
  @dogs.each {|dog| dog[:fear_factor] += 5}
  #Output : @dogs contains [{:name => "Benji", :fear_factor => 8},{:name => "Rex", :fear_factor => 12}]

Do you think of using select, reject and collect/map, or does the each slippers have too much influence on your life?

4 thoughts on “Life beyond the each iterator

  1. great article.
    I personally have come to love my new shoes, uniq! for those that don’t know, if removes all the duplicates in the array. took me awhile to find that one. I mean, i knew ruby could do it, I just couldn’t find out what it was named…

  2. Because each is like the first thing we learn when we begin Ruby, we think that it’s the only one of its kind. I feel ashamed to learn these new functions after almost a year of developing in Ruby…

  3. Another important point to consider is that the other iterators will most likely outperform the implementations that use each. The iterators in the base classes are implemented directly in C for the most part, and thus should eliminate a lot of the overhead and garbage collection that slow down using pure Ruby solutions based on each.

Leave a Reply

Your email address will not be published. Required fields are marked *