I remember when I first started looking at Ruby, I’d be browsing some code and see yet another way of looping/iterating over stuff. Whenever that would happen I would think, “…Ruby sure has a lot of different ways to iterate over things”, but I also remember wishing that someone would just put all the different ways to loop and iterate over stuff together so that you don’t have to discover them in a piecemeal fashion. Most resources I’ve seen tend to gloss over this a little bit by introducing the while loop and the each iterator and quickly moving on to more interesting things, expecting you to discover the rest on your own (nothing wrong with that by the way).
As you may have guessed from my recent posts on Ruby method arguments (and more advanced method arguments), the Ruby case statement and others, I am kind-of discovering Ruby in my own way and looking more closely at things that I find of interest. So I’ve decided to fulfill my own wish and put together all the different ways to loop over stuff in Ruby.
Ways To Loop Over Stuff In Ruby
I classify looping and iterating in Ruby into two distinct buckets:
- simple ways to loop/iterate – this is where we loop over elements and work with each element as we iterate over it, but we basically don’t need to retain any knowledge of what we did to a particular element once we move on to the next unless we explicitly decide to store some info (this is how the basic loops and iterators operate)
- complex ways to loop/iterate – this is where in addition to iterating over elements we transform the elements we are iterating over in some way and retain this information when we complete the loop (this is how more complex iterator-style methods such as map, collect etc. work)
I will only be covering simple ways here, there are more than enough of those, and I will look at more complex ways at a later point. Anyways lets dive in.
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:
ruby
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 break, next and redo keywords. The break keywords allows us to exit a loop at any point e.g.:
ruby
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:
ruby
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:
ruby
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:
ruby
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:
ruby
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 until loop 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:
ruby
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:
ruby
i=0
print "#{i+=1} " while i < 10
ruby
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:
ruby
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:
ruby
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.:
ruby
for i in 1..10
print "#{i} "
end
or values in an array e.g.:
ruby
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.:
ruby
[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..end block 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.:
ruby
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:
ruby
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.:
ruby
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 for loop (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 _eachindex iterator comes in handy:
ruby
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 the redo 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 the retry keyword can not be used within while and until loops and similar.
That’s all the simple ways to loop/iterate over stuff in Ruby. If you think I’ve missed some or simply have something to add feel free to let me know in the comments. In the meantime enjoy getting loopy (and iteraty ) :).
Image by PanCa SatRio