Sunday, 2 November 2008


Ruby has several options for loops; I will start with for and each loops. In practical terms, there is not a lot between for and each besides style, though each is actually a method call that takes a block, while for seems to be a language feature. They both iterate over a collection, which could be a hash, a string, an array or a range. Let us look at ranges first.

For and each with ranges
Use of a range takes the place of the normal for/next loop. In these examples, 1..10 and 1...11 are Range objects; the three dots indicates that the end stops before the terminator, two dots indicates it includes the terminator. These three examples will all print out the numbers 1 to 10, illustrating the difference between each and for, and the two and three dots.
for i in 1..10

for i in 1...11

(1..11).each do i

Ruby is perfectly happy with characters and variables in a Range object, and the next example shows how a range can be assigned to a variable.
r = 'a'..'z'
for i in r
puts i

n = 5
(1..n).each do i
puts i

These loops output the Range object, by the way, and you can test if something is within a range using the member? method (eg r.member? "c").

A quick note about arrays and ranges. I got really confused by putting square brackets around a range. What is [1..5]? This is an array with a single member, and that member is a Range object.

For and each with arrays, hashes and strings
The each method allows you to iterate over each member of a string, an array or a hash.
array.each { member do_stuff_with(member) }
array.each do member

For a hash, you pass two values to the block
hash.each { key, value do_stuff_with(key, value) }
hash.each do key, value
do_stuff_with(key, value)

You can use the for feature with hashes and arrays too, for example:
h = {:a => 19, :b => 35, :c => 3456}
for k, v in h
puts "key=#{k} value=#{v}"

The times method
In Ruby everything is an object, and integer objects (of the class Fixnum) have a method called times, which, like each, accepts a block, and iterates from zero up to one less that the number. In these examples, the numbers zero to nine are printed.
10.times { i puts(i) }

10.times do i
puts i

n = 10
n.times { i puts i }

Invoking times on a negative number will create a loop with zero iterations!

The yield feature
You can easily define your own methods that work like each and times, using the keyword yield. Say this method is defined in a class, Car:
def cars
yield "Red car"
yield "Green car"
yield "Blue car"

The following will then produce a list of cars; red, green and blue.
m = { c puts c }

The cars method runs until it hits the yield, and at that point passes the value back to the calling code, which then processes it as required. Then the loop goes again, and the cars method continues to the next yield stament. The loop iterates until it runs out of yields (the cars method will run to the end regardless of any further yields in the code).

The while and until loops
Ruby also supports while and until. The until loop is just a while not loop. There appears to be no option to check the conditional after the iteration that I have found.

Infinite loops
You can also create infinite loops with the loop method. Hmm, beter make sure there is some way to break out of the loop, which brings us to:

The break, redo, retry, next and return keywords
Ruby also has some interesting keywords to modify loop behaviour. As with C and derivatives, the break statement drops you out of the current loop. The next statement replaces continue; it stops the current iteration, moves to the next one, and passes control to the condition at the end of the loop. The retry statement is similar, but does not move to the next iteration; it repeats the current one. The redo statement starts the whole loop from the first iteration. There is also a return statement, which drops you straight out of the entire method (and optionally takes a parameter; a value returned by the method).

Struggling with Ruby: Contents Page

No comments: