logo

Doing a Ruby Loop the Ruby Way

logo

The best thing about Ruby loops is that you hardly need to use them. Well, at least not in the familiar way if you are coming form a procedural language such as C++, PHP, Java, etc. In this article you’ll see why conventional loop constructs such as while loops and for loops need only be used on rare occasions in Ruby, and how those give way to concise, easy to use, and very powerful syntax.

The reason why Ruby can avoid conventional loops is largely because in Ruby, most looping can be accomplished by commanding collection objects to go through their content and call a block of code with each iteration. Yes, it’s technically still a loop, but simpler to write, less error prone than the C like loops, and looks more like a method call than a loop.

Okay, onward an away to Ruby loopless loop techniques…

Looping Using the times Method

The first and simplest loopless Ruby loop technique is a call to the method times on an object that represents an integer such as Fixnum or Bignum. This exhibits very simple syntax that’s great to use when repeating something a given number of times.

5.times { print '*' }

This prints out an asterisk character 5 times.

This syntax seemed very striking when I was first learning Ruby. The ability to call a times method on a literal constant integer and have it loop the number of times indicated by the integer seemed amazing. But of course, this is not exactly what’s happening. Since everything in Ruby is an object, what may seem like a constant literal integer is also an object.

We can also call times on a variable:

num = 5
num.times { print '*' }

The times method takes a block between the curly braces { } or a doend pair and calls the block the number of times indicated by the number it was called upon.

The block can also be given a single argument, into which the iteration number is passed, starting with zero:

5.times { |x| print x }

Outputs

01234

Like the times method, upto can also be called on a variable.

Looping Using the each Method of a Range

The next loopless Ruby loop technique is the each method call on a Range.

(2..5).each { |x| print x }

Outputs

2345

Every Ruby collection class defines an each method whose function is to iterate through every item in the collection and pass it to a given block one by one. (A collection class represents collections of things. Examples include Array, Hash, and Range.)

In the above code example, the range is formed by the 2..5 construct. The parentheses are then added, (2..5), because of precedence rules. Omitting the parentheses and adding a call to the each method, 2..5.each, would unfortunately cause a call to the non-existent each method of the number 5.

The range need not necessarily be defined on integers. Most notably, ranges work with characters 'a'..'z' and strings 'ab'..'bc'. Ranges can also work with user defined classes that implement certain methods that act as plumbing for the workings of the Ruby Range class, but that’s a topic for another discussion.

Looping Using the upto Method

The upto method provides a variation on the above looping technique on a Range. At this point it’s simple enough to just show some examples:

2.upto(5) { |x| print x} # prints 2345
'a'.upto('e')  { |x| print x} # prints abcde

Looping With the Range step Method

The Range class also defines a method called step that passes every nth element to the block. This brings us to the third technique.

(2..8).step(2) { |x| print x }

Outputs

2468

Looping Using the Enumerable Methods

The fourth technique is actually a whole toolbox. Ruby’s Enumerable module augments every Ruby collection class with a set of methods that build on a collection’s each method.

This set of methods provides for many ways of going through all items in a collection and doing something with them. The Enumerable module alone account for nearly eliminating the need for conventional looping syntax (which still exists by the way) in Ruby.

Let’s look at one of the most common Enumerable methods, map.

Map iterates through each element of a collection, passes it to a block, and adds the result of the block to an array.

First let’s see how map works on a collection that happens to be an Array:

arr = [1,2,3,4,5]
arr.map { |x| x*2 }

This evaluates to [2,4,6,8,10].

Note that the returned value is a new array. The original arr is still unaffected and equals [1,2,3,4,5]. The method map! will change an array in place.

For the sake of contrast, let’s examine how the above could be done using a Ruby for loop:

arr = [1,2,3,4,5] # the original array
tmp = [] # a new array to hold the result
for i in 0...arr.size # loops from 0 to arr.size -1, the a...b range is the same as a..(b-1)
  tmp << arr[i]*2 # add the desired value to the new array
end
return tmp

As you can see, the map method makes things a lot simpler! In particular, you don’t have to worry about indexing the array and a possible index out of bounds error.

Using a map on a Hash is a little more nuanced. If you use a one argument block, map will pass a key/value pair as an array of two elements, [key, value], in that argument. If you use two arguments, map will pass the key in the first and the value in the second.

Let’s see how this works:

h = { 1 => 'a', 2 => 'b', 3 => 'c'  }
h.map { |x| "#{x[0]}:#{x[1]}" }  # single argument block
h.map { |x,y| "#{x}:#{y}" } # double argument block

Both calls to map result in the array ["1:a", "2:b", "3:c"].

Aside from map, Enumerable has many more methods that do wonderful things by iterating on collections. Discussing all those is beyond the scope of this article and you can learn more by reading the Enumerable Ruby docs.

Summary

Here’s a summary of the looping techniques discussed on this page:

num.times { block } #  loops num times

num.times { |x| block } # loops num times passing the 0-offset index in x

(a..b).each { |x| block } # loops from a to b

a.upto(b) { |x| block } # loops from a to b

(a..b).step(n) { |x| block } # loops a to b with n sized steps

collection.map { |x| block } # uses an Enumerable method to process all members of a collection

Tags: , , , , , ,

Leave a Reply

logo
logo
Copyright © 2014 by Yaron Walfish. All rights reserved Powered by WordPress | Designed by Elegant Themes