Tuesday, March 1, 2011

Ruby Loops And Iterators


Unconditional Looping With Loop

The simplest looping construct in Ruby is the loop method. Technically speaking it is not a looping construct but is infact an iterator method since it takes a block, but because it is the simplest of all it comes first. The simplest version of it is the infinite loop:
loop {puts "HELLO"}
This will just infinitely print out HELLO, not particularly useful, but if you ever need a simple way to create an infinite loop, there you go :).
Of course Ruby provides us with all the loop termination and control keywords that allow us to make the loop method more useful than it has been so far. I am talking about the breaknext and redo keywords. The break keywords allows us to exit a loop at any point e.g.:
i=0
loop do
i+=1
print "#{i} "
break if i==10
end
This will print out the numbers from 1 to 10 all on the same line:
1 2 3 4 5 6 7 8 9 10
Since we are using the break keyword, the loop will exit when the value of i hits 10.
You can use the next keyword to skip over the current iteration of the loop and go on to the next one:
i=0
loop do
i+=1
next if i==3
print "#{i} "
break if i==10
end
This will print out the numbers from 1 to 10 all on the same line, but will skip number 3 because we skipped that iteration of the loop:
1 2 4 5 6 7 8 9 10
You can also pass a value to both next and break, but it is only useful in the case of break as it will become the value that the loop returns (remember, every expression in Ruby returns a value), so we could do something like this:
i=0
puts(loop do
i+=1
print "#{i} "
break 'Hello' if i==10
end)
This will print out the numbers from 1 to 10 followed by ‘Hello’ since that is the value that the loop expression will return:
1 2 3 4 5 6 7 8 9 10 Hello
I also mentioned the redo keyword, but it makes little sense to use it with the loop method, so I will save it for later.

The While Loop

The while loop in Ruby is just like the standard while loop in any other language nothing too fancy:
i=1
while i < 11
print "#{i} "
i+=1
end
This will print out the numbers from 1 to 10 as you would expect:
1 2 4 5 6 7 8 9 10

The Until Loop

The until loop is similar to the while loop but the logic is reversed:
i=1
until i > 10
print "#{i} "
i+=1
end
Once again we print out the number 1 to 10 as expected, as you can see the loop condition for the untilloop is the opposite of the while loop:
1 2 4 5 6 7 8 9 10
I did mention I was gonna cover the redo keyword, well now is as good a time as any. The redo keyword allows you to restart the loop from the beginning without evaluating the condition again. It is not very useful in the examples we’ve been doing so far where our terminating condition is affected by what we do within the loop (e.g. incrementing the value), all we can do is create a more fancy infinite loop that will keep printing out ever incrementing numbers:
i=1
until i > 10
print "#{i} "
i+=1
redo if i > 10
end
This will keep incrementing the value of i and will keep printing it out:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ...
The reason this happens is because redo restarts the loop from scratch but does not evaluate the terminating condition, so even though i is greater than 10 we still do another iteration and increment the value and then hit the redo again and go for another ride (yee-haw :)).

Using While And Until As Modifiers And Simulating Do..While

Ruby also allows you to use the while and until keywords as modifiers, which means you can put them at the end of an expression just like you can do with the if statement. You can therefore create much shorter versions of the loops above:
i=0
print "#{i+=1} " while i < 10
i=0
print "#{i+=1} " until i == 10
Both will print out the numbers from 1 to 10 all on the same line as has become the custom for us :).
This property of the while and until keyword also allows us to simulate the do..while loop in Ruby. Ruby has no looping construct that is guaranteed to always execute at least once (like the do..while), but we can do the following:
i=11
begin
print "#{i} "
i+=1
end while i < 10
This will print out the number 11, even though we’ve set the loop to terminate when the value of i is less than 10, the loop is guaranteed to execute at least once, so we get the value of i printed out once.
We can do something similar with until:
i=10
begin
print "#{i} "
i+=1
end until i == 11
This will print out 10 and then exit the loop as the exit condition will be reached.

The For Loop

If we discount the loop method then the for loop acts as a kind of bridge between looping constructs and iterators in Ruby. The for loop is still a looping construct but it acts almost like an iterator without actually taking a block. You can use the for loop to loop over values in a range e.g.:
for i in 1..10
print "#{i} "
end
or values in an array e.g.:
for value in [1,2,3,4,5,6,7,8,9,10]
print "#{value} "
end
Both of these will once again print out the numbers from 1 to 10.

The Each Iterator

Now we come to iterators. Iterators are methods that take blocks and execute that block as many times as there are iterations. There are simple iterator methods and more complex ones (we are only looking at the simple ones), the simplest iterator method is – each. All iterables (such as arrays and hashes) in Ruby will have an each method that will allow you to loop over the values in the iterable and do something with each one. For example you can iterate over the values of an array using each and pass in a block that will print out each one e.g.:
[1,2,3,4,5,6,7,8,9,10].each {|value| print "#{value} "}
You know what this will print out :). If you want to do something more complex you can use the do..endblock syntax and have all sorts of fun with each of the values you’re iterating over. Simple, moving on.

The Times Iterators

The times iterator is similar to you classic for loop in other languages and will allow you to execute a loop and perform an action (according to the block you write) x number of times e.g.:
10.times {|i| print "#{i} "}
This will print out the numbers 0 to 9, so we’re really bucking a trend here.
0 1 2 3 4 5 6 7 8 9
Because we are using the times iterator we are only interested in how many times we iterate rather than what each value will be so 0 to 9 is ok. Times is a really useful shortcut when you have a number and need to iterate that many times. If you do need to control what values the block will get on each iteration as well as how many times you iterate you can use the upto and step iterators.

The Upto And Step Iterators

Once again it is similar to your classic for loop in that we execute from number x up to number y (y needs to be bigger than x obviously). So if we want to get back to printing 1 to10 we can do the following:
1.upto(10) {|i| print "#{i} "}
This prints out what we expect:
1 2 3 4 5 6 7 8 9 10
We can also iterate while skipping over a range of numbers on every iteration e.g.:
1.step(10, 2) { |i| print "#{i} "}
This skips 2 rather than skipping 1, which is equivalent to doing i+=2 if you were using your classic forloop (or a while loop), so we end up printing out the following:
1 3 5 7 9
We are in the home stretch now only one more to go.

The Each_Index Iterator

Sometimes we have an array and we don’t want to loop over every value but rather want to loop over every index, this is where the each_index iterator comes in handy:
array = [10,20,30,40,50,60,70,80,90,100]
array.each_index {|i| print "#{array[i]} "}
The block gets the index of the array on every iteration and we can then use that to print out the value (not useful but illustrates the point):
10 20 30 40 50 60 70 80 90 100
As expected we printed out all the values in the array, it is 1 to 10 but 10 times bigger for extra awesomeness :).
There is one last thing to mention about simple loops and iterators, the retry keyword. I mentioned theredo keyword, way up above towards the start, which can restart a while or until loop without re-evaluating the condition. Well, the retry keyword is similar but it can be used for iterators and for loops. The retry keyword will also force an iterator or for loop to restart but it will re-evaluate the arguments passed to the iterator. Note that the redo keyword can also be used for iterators and the for loop but theretry keyword can not be used within while and until loops and similar.

No comments:

Post a Comment