These free mini-courses will give you a strong foundation in web development. Track your progress and access advanced courses on HTML/CSS, Ruby and JavaScript for free inside our student portal.
Scroll down...
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.
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
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
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
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.
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"}
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).
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
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
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
As we've said before, just use the simplest possible loop to get the job done. Usually there are only three reasonable candidates:
loop
for deliberate infinite loops (yes, sometimes you will).while
for anything that needs to run until a certain condition is reached (like winning the game)each
for any time you want to do stuff with every single item in an array or hashtimes
for the simple cases when you just want to do something a fixed number of times.for
never.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.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 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