A Wealth Of Ruby Loops And Iterators

LoopI 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:

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.:

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 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:

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..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.:

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

  • Daniil

    That was a really good article! Very well written and informative

    • http://www.skorks.com Alan Skorkin

      Thanks mate, I appreciate the comment and I am glad you liked it.

  • http://ananasblau.com Thomas R. Koll

    Nice overview :)
    I use the each like 95% of the time, I even use [1..10].each instead of 1.upto(10) as it’s more compact.

    • http://www.skorks.com Alan Skorkin

      Yeah I was the same, you just end up using the thing you’re most familiar with, but it is important to remember that there are other tools that will work for the same job, hence the post.

    • monty

      [1..10].each is more compact in what sense?

      I’m a big fan of ranges, but you can’t do a reverse range, e.g., [10..1].each. For that, 10.downto(1) is the most readable and compact flavor I’ve seen (and downto isn’t covered elsewhere in Alan’s nice survey article).

  • http://cubeantics.com Robert

    Nice post, I did something similar with a Ruby tutorial I wrote last week (http://cubeantics.com/an-introduction-to-ruby-part-3-program-control). I really like the way you presented the information it is very clear and informative.

    • http://www.skorks.com Alan Skorkin

      Thanks man, I try to write stuff the way I would have liked to have read it, I usually find I am nt the only one who would have liked the information presented or aggregated differently as well as going into more detail about what is happening under the hood.

  • Bob Schäfer

    i’m glad you mentioned “each_index”. the standard library is lush with “each_noun” methods. they are not only convenient, i feel that they’re good examples of how to *think* about programming with objects. i’m fond of Hash.each_key and String.each_byte and especially File.each_line. keep up the good work, alan!

    • http://www.skorks.com Alan Skorkin

      Yeah the each* iterators are great, I didn’t want to mention all of them since there are so many, but I thought they deserved some mention :).

  • JSebok

    Nice work, very helpful as a reference!

    I think .each_with_index {|item, index| … } worth to mention, if one uses sparse arrays.

    I recently also came across the fact, that Ruby 1.9 does not support the ‘retry’ keyword for looping anymore.

    • http://www.skorks.com Alan Skorkin

      Agreed, each_with_index is definitely a good one to know and cheers on the heads up about ‘retry’ in 1.9, i’ll have to look into that one.

  • Chris

    You forgot each_with_index! I use it all the time.

    ["you", "forgot", "this", "iterator"].each_with_index do |value, index|
    puts “#{index} : #{value}”
    end

    • http://www.skorks.com Alan Skorkin

      As I mentioned in a previous comment I didn’t want to go through all the each* methods, but perhaps each_with_index deserved a mention.

  • http://[email protected] Mark Wilden

    Add ‘for each x in y’.

    • http://www.skorks.com Alan Skorkin

      I’ve got ‘for’ loop examples that are fairly representative, but you’re right that for loop is pretty flexible in what you can enumerate with it.

    • Chris

      i can’t seem to get that one working

      • http://www.skorks.com Alan Skorkin

        If you’d like to paste in some code and what you’d like to do with it, I’d be happy to have a look for you.

    • bruce

      for x in y is EXACTLY the same as y.each

  • Pingback: Chain Links #025 | Proc#curry

  • http://elabs.se Jonas

    What about the each_with_index iterator? Might also be worth mentioning select, find, map, inject and friends. Also enumerators in Ruby 1.9 are pretty cool :P

    • http://www.skorks.com Alan Skorkin

      A few people have mentioned each_with_index now :), I am planning to do some posts about enumerators and collect, select, inject, reject etc. I am just trying to get it straight in my head how to deliniate it all in a logical manner, like I said in one of the comments above, I like to write it the way I would have wanted to read it rather than just dumping info.

  • http://twitter.com/ehoque ehsanul

    Nice for newcomers. You should include iterators over hashes if you want to be a comprehensive source: each, each_key and each_value

    • http://www.skorks.com Alan Skorkin

      I couple of people have mentioned those in the comments now, including yourself, which I really appreciate, I like to be as thorough as I can but I know that I won’t be able to think of everything, so it is really good to see when others get involved and contribute to fill in the blanks.

  • Steve Smith

    Great article. Its always nice to read stuff like this. I think often you can get set in your ways only using your favorites.

    I also find ranges are quite a nice looking way to loop sometimes (1..10).each

    • http://www.skorks.com Alan Skorkin

      Exactly, that’s how I feel too, aside from learning this kind of stuff for the first time, sometimes it is good to revisit what you may think are the basics. You’d be surprised how much insight you can extract when you have more experience.

  • Pingback: Notional Slurry » links for 2009-09-02

  • Michelangelo

    Great article! This should be really useful for beginners hanging around at my local ruby user group! Thank you!

    • http://www.skorks.com Alan Skorkin

      You’re very welcome, everybody is a beginner at some point :)

  • http://railsgeek.com mikhailov

    what’s about map, map!, collect, collect! and more complex inject, reject, zip methods?

    • http://www.skorks.com Alan Skorkin

      Those are a bit more complex than your basic iterators so I want to cover them in seperate post.

  • Pingback: Ennuyer.net » Blog Archive » Rails Reading - Sept 10, 2009

  • Alexey Ignatiev

    Thank you. This is really useful article. I get some tests concerning loops in ruby. Maybe you would be interested in it. http://2sev.blogspot.com/2009/10/loops-in-ruby.html

    • http://www.skorks.com Alan Skorkin

      You’re welcome. Those are interesting timing tests. The thing to remember is that C has been around for ages and is very stable and low level. Both Ruby and python interpreters are still improving and will continue to get faster and faster with every release just like Java did in it’s day.

  • Pingback: Итераторы и циклы Ruby | Разработка на Ruby и Rails c нуля

  • Pingback: Times, UpTo y Step: De Ruby a C# | reThink

  • Peter

    Hi
    Can you suggest how to interate an array and at each iteration to (sub)iterate backwards from the current position X times. So the outer interation really wants to give a sub array to the inner iteration rather then just the current element

    Peter

    • http://www.skorks.com Alan Skorkin

      I am not sure exactly what you mean, can you give an example.

  • [email protected]

    the sections ‘The While Loop’ and ‘The Until Loop’ provide wrong examples. They should produce “1 2 3 4 5 6 7 8 9 10″, but the digit “3″ is skipped somehow. Just figured out;)

  • varnie29a

    hey,buddy. your examples for the ‘until’ and ‘while’ loops produce wrong output. fix’em please;)

  • Pingback: The Rubyist Historian: Loops and Control Structures | History in the Digital

  • Pingback: What I learned today: The for iterator « danny garcia

  • ZM

    spared me 150 LOC out of 200 LOC

  • Beka

    wonderful compilation and great article. thank you!

  • Pingback: For Loops in Ruby

  • Pingback: What I did today 12/5/13 | A Single Step – ghdev.net

  • Myles Howard II

    I’m a newbie to Ruby and I really liked the article. I just have a question about the step iterator. Is the first number (10) the iterator control number? How would the program know to stop after 9?

  • Agasthiyaa

    i=0
    puts(loop do
    i+=1
    print “#{i} ”
    break ‘Hello’ if i==10
    end)

    I need above code for string ?????