Ruby Explained: Iteration
This post will get into Ruby loops and flow control
You can assemble code, tell the program which parts of it to execute, and wrap it all up in a method. 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.
You should understand the basic iterators for
and while
and understand how to use #each
and #times
. We’ll talk more about blocks and the other Ruby iterators like #map
and #select
in the next posts, so it should be more obvious how #each
and #times
work after reading that.
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:
> loop { puts "this will not stop until you press CTRL+c" }
this will not stop until you press CTRL+c
this will not stop until you press CTRL+c
... and so on
> i=0 # Our index variable
> loop do
> 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, by allowing you to specify the condition that must be true to keep looping, and 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 declare the variable(s) you’ll be using (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
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
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 that array or hash. #each
will automatically pass whichever item it is currently on into your code block. That item will be named whatever name you specify inside the pipes | name_goes_here |
:
> guys = ["Bob", "Billy", "Joe"]
> guys.each do |current_name| # better to call it just "name" in your code
> print "#{current_name}! "
> end
Bob! Billy! Joe! => ["Bob", "Billy", "Joe"] # returns original array
Many other loops are just you 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, it will take the value of the current iteration (starting from zero):
> 5.times do |jump_num|
> print "Jump #{jump_num}!"
> end
Jump 1!Jump 2!Jump 3!Jump 4!Jump 5!=> 5
A couple other methods with similar purposes to #times
that you see less frequently:
-
#upto
, as in1.upto(4) { |current_number| ...some code... }
, just like#times
but you choose the starting and ending point instead of always starting at zero. -
#downto
, similar to#upto
but… down… to….
Your best friends early on will be 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 item in an array or hash, and #times
for the simple cases when you just want to do something a fixed number of times.
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 anif
to specify under what conditions to do that. -
next
will jump to the next iteration. Also usually used with anif
statement. -
redo
will let you restart the loop (without evaluating the condition on the first go-through), again usually with some condition -
retry
works on most loops (notwhile
oruntil
) similarly toredo
but it will re-evaluate the condition before running through the loop again (hence try instead of do). - NOTE: Do NOT use
return
to exit a loop, since that will exit the whole method that contains it as well!
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
*The “Ruby Explained” posts are designed to be a sort of “In-Plain-English” version of key Ruby concepts which are usually covered in other introductory texts but rarely for free and often incompletely. When I’m learning a new thing, I usually want someone to explain it to me like I’m a five year old because that’s the best way to make sure nothing gets missed. This is my attempt to pass that same sentiment on to you. Let me know if there’s anything I can improve.*
If you’re just getting interested in this stuff, check out The Odin Project for a free curriculum to learn web development.