Life beyond the each iterator

by Frank

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.

sdc_candles1_md1.jpg

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!”

0410653194313_275×275.jpg

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.

select

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 = @dogs.select {|dog| calculate_emotional_intelligence(dog) >= 50}

reject

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]
end

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
  @dogs.map! {|dog| dog[:fear_factor] + 5}
  #Output : @dogs contains [8,12]
end

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}]
end

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

Bookmark this post : These icons link to social bookmarking sites where readers can share and discover new web pages.
  • DZone
  • Reddit
  • del.icio.us
  • Digg
  • Furl
  • Technorati
  • StumbleUpon