Iteration

Making it happen again. Making it happen again. Making it happen again...

Scroll down...

Content

Resources

Comments

You can now assemble code, tell the program which parts of it to execute, and wrap it all up in methods. There's still something missing... what if you want to make something happen a whole bunch of times?

You certainly don't just run the method again and again manually. Luckily we've got several standard ways of iterating through a piece of code until we tell the program to stop.

In this lesson, we'll cover loops and flow control. You should understand the basic iterators like loop and while and also how to use each and times. We'll talk more about blocks and the other Ruby iterators like map and select in upcoming lessons.

Loops and Index Variables

A loop is really just code that will run a number of times until some condition is met. A variable is typically used to keep track of which iteration you are on or to otherwise increment until the condition is reached. This is called the index variable.

Loop

loop is the most basic way to loop in Ruby and it's not used all that much because the other ways to loop are much sexier. loop takes a block of code, denoted by either { ... } or do ... end (if it's over multiple lines). It will keep looping until you tell it to stop using a break statement:

# Single-line infinite loop
> loop { puts "this won't stop until you press CTRL+c" }
this won't stop until you press CTRL+c
this won't stop until you press CTRL+c
# ... and so on

# multi-line loop
> i=0             # Our index variable
> loop do         # Note that i is a bad variable name
>   i+=1
>   print "#{i} "
>   break if i==10
> end
1 2 3 4 5 6 7 8 9 10 #=> nil

While

while performs a similar function but in a much more compact way. It allows you to specify the condition that must be true to keep looping. You'll find yourself using it much more in your own code. It doesn't actually take a formal block of code, just runs everything until it reaches its end.

Just remember to initially declare the variable(s) you'll be using OUTSIDE the loop (or they'll get reset with each iteration) and to increment at some point (or you'll get stuck in an infinite loop... use ctrl+c to break in Terminal):

> i=1
> while i < 5
>   print "#{i} "
>   i+=1
> end
1 2 3 4 #=> nil

Until

until is almost identical to while but, instead of running as long as the specified condition is true, it runs as long as the condition is false. It's the same logic as using unless versus if for conditionals.

Each

Things get more interesting when you realize that most of your loops will probably involve looping over each element in either an array or a hash of items. Ruby knows this and made it super easy for you by specifying the each method that you call directly on the array or hash.

each will automatically pass the "current" item into your code block. That item will be named whatever name you specify inside the pipes, e.g. (for an array) | variable_name_goes_here |:

> guys = ["Bob", "Billy", "Joe"]
> guys.each do |name|  
>   print "#{name}! "
> end
Bob! Billy! Joe! #=> ["Bob", "Billy", "Joe"]  
# Note that each returns original array!!!

each always returns the original array. Remember this!

each for hashes inputs two arguments, one for the key and another for the value:

> guy_fav_colors = {  "Bob" => "yellow", 
                      "Billy" => "red", 
                      "Joe" => "green" }
> guy_fav_colors.each do |name, color|  
>   puts "#{name} likes #{color}! "
> end
Bob likes yellow! 
Billy likes red! 
Joe likes green! 
#=> {"Bob"=>"yellow", "Billy"=>"red", "Joe"=>"green"} 

Times

You often loop when you're trying to do something a certain number of times (which was the case in our for loop example). In that case, Ruby has the simplest possible method for you: times.

If you pipe in an argument, that argument will take the index of the current iteration (starting from zero):

> 5.times do |jump_num|
>     print "Jump #{jump_num}!"
> end
Jump 0!Jump 1!Jump 2!Jump 3!Jump 4!#=> 5

times returns the FixNum that you called it on (5 in the example above).

Looping with an Array Index Value

Sometimes you want to loop through every item in an array but also know where you are in that array. You can actually do this with both while and times but the each_with_index method is specially designed for it since it passes the block not just the current item but the item's index.

Several ways to loop through an array while preserving access to its index value:

> nums = [5,2,7,9]

# while
i = 0
while i < nums.length
    puts "num at #{i} is #{nums[i]}"
    i+=1
end
num at 0 is 5
num at 1 is 2
num at 2 is 7
num at 3 is 9
#=> nil

# times
> nums.length.times do |i|
>   puts "num at #{i} is #{nums[i]}"
> end
num at 0 is 5
num at 1 is 2
num at 2 is 7
num at 3 is 9
#=> 4

# each_with_index
> nums.each_with_index do |num, i|
>   puts "num at #{i} is #{num}"
> end
num at 0 is 5
num at 1 is 2
num at 2 is 7
num at 3 is 9
#=> [5, 2, 7, 9] 

For

for is a looping mechanism present in lots of other languages but it gets de-emphasized in Ruby and you don't see it used much. A common use is to loop over every number in a range. You name the variable that holds the current number at the top and can then access it from inside the loop:

> for a_number in (1..3)
>   print "#{a_number} "
> end
1 2 3 #=> 1..3

Less Common But Helpful Methods

A couple other methods with similar purposes to times that you see less frequently:

  • upto is just like times but you choose the starting and ending point instead of always starting at zero.
  • downto, similar to upto but... down... to....
> 1.upto(4) { |num| puts "#{num}" }
1
2
3
4
#=> 1
> 4.downto(1) { |num| puts "#{num}" }
> 4
> 3
> 2
> 1
#=> 4

Looping Best Practices

As we've said before, just use the simplest possible loop to get the job done. Usually there are only three reasonable candidates:

  1. Use loop for deliberate infinite loops (yes, sometimes you will).
  2. Use while for anything that needs to run until a certain condition is reached (like winning the game)
  3. Use each for any time you want to do stuff with every single item in an array or hash
  4. Use times for the simple cases when you just want to do something a fixed number of times.
  5. Use for never.

Controlling Your Loops

Because you may want some additional control over your loops, use these statements to jump in and out of them for certain abnormal conditions:

  • break will stop the current loop. Often used with an if to specify under what conditions to do that.
  • next will jump to the next iteration. Also usually used with an if statement.
  • redo will let you restart the loop (without evaluating the condition on the first go-through), again usually with some condition. This is rare.
  • retry works on most loops (not while or until) similarly to redo but it will re-evaluate the condition before running through the loop again (hence try instead of do). Rare.

Returning From A Loop

Loops aren't methods that take a return statement! Do NOT use return to exit a loop, since that will exit the whole method that contains it as well!

def say_items(items)
    item_i = 0
    while item_i < items.length
        puts items[item_i]
        return "Too big!" if item_i >= 3
        item_i+=1
    end
    puts "There were less than 4 items"
end

# IRB
> say_items([1,2,3])
1
2
3
There were less than 4 items
#=> nil
> say_items([1,2,3,4,5,6,7,8,9])
1
2
3
4  # puts didn't run because we returned from the method
#=> "Too big!"

Nesting Loops

Nesting loops occurs when one goes inside another, so you execute the entire inner loop for each iteration of the outer loop. You'll see those for "two-dimensional" problems, like searching through arrays within arrays, but if you find yourself nesting too often or too deeply, you probably need to reexamine how you've structured your solution overall.

Here's an example that goes through the comments in a hypothetical blog and captures a preview from each of them. It uses some Rails methods to get the posts and comments then loops to tease out the previews:

def comment_previews
  comment_previews = []
  posts = Post.all              # array of all blog posts
  posts.each do |post|
    comments = post.comments    # array of that post's comments
    comments.each do |comment|
      comment_previews << comment[0..80] 
    end
  end
  comment_previews
end


Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

There are no additional resources for this lesson just yet!

Sorry, comments aren't active just yet!

Next Lesson: Dates and Times