So far, it doesn't seem that programming is saving us much time at all, right? We've basically had to type just as much or more in code than we get back as output. So where are the productivity gains?
Through loops. Unlike an engine that obeys the laws of thermodynamics, a program can easily output millions times over what you write in code. Loops are a construct that allows a program to execute thousands of commands in less than a second, with just a few lines of code.
The for Statement
Typing out similar statements is tedious:
puts "Hello world, this is number 1"
puts "Hello world, this is number 2"
puts "Hello world, this is number 3"
# and so on...
Using a for loop, we can repeat a command as many times as we want. We can also vary its output depending on how many times it has run:
for current_iteration_number in 1..100 do
puts "Hello world, this is number #{current_iteration_number}"
end
Running the above code should print out 100 lines, with the current_iteration_number variable increasing by 1 each time.
Admittedly, this isn't an exciting program. But replace that puts command with any other command(s), and you'll get to the core of why learning to code is so useful: repetitive tasks can be done a thousand times over with just a few lines of code.
Let's examine the above for loop code.
- for
- A special Ruby keyword that indicates the beginning of the loop.
- current_iteration_number
-
This is a variable name that serves as the reference to the current iteration of the loop. We can name it however we like. For loops, I usually choose a short name such as i, because it is a variable that I won't plan to reuse outside of this loop.
In this example, current_iteration_number starts with the value of 0. During the 20th (actually, the 21st, as the first iteration is 0) time through the loop, current_iteration_number will be equal to 20. When this loop finishes, current_iteration_number will be 100.
- in
- This is a special Ruby keyword that is primarily used in for loops. I can't recall when I've ever used it elsewhere.
- 1..100
-
This is a Ruby class called a Range. It consists of a low and a high number separated by two dots. Basically, it is the set of integers from a to b, sequentially.
In this case, a is 1 and b is 100. So, you can read this entire line as: for every number in 1 to 100, run the following code block.
- do
- This indicates the beginning of the block of code to be repeatedly executed. In a for loop, the do is optional. But I introduce it here as do is used throughout Ruby to start off a block.
- end
- This keyword is simply the bookend for the code block that started with do.
Note: The for loop isn't used much by Rubyists, as you'll see in the enumerables and collections chapters. But the syntax is pretty standard across many languages, so I cover it here before getting into the more Ruby-specific idioms.
Exercise: Simple loop
Let's practice the for loop before moving on. Using if statements, write a loop that prints out all numbers, from 1 to 1000, that can be divided by 7 without any remainder.
Hints
- You can use the division sign / But the modulo operator, %, may be more efficient.
- A number that is evenly divisible by 7 has a remainder of 0
Solution
for i in 1..1000
puts i if i % 7==0
end
Exercise: Inner loops
For numbers 1 through 1000, print out how many times each number can be divided evenly with the numbers ranging from 1 to 25. A number x can be said to "divide evenly" by y, if x / y has no remainder.
For example, in the range of 1 – 1000, there are 1,000 numbers that are evenly divided by the number 1. There are 500 numbers in that range that divide evenly by 2, and so forth.
Your output should look like this:
There are 1000 numbers divisible by 1, from 1 to 1000 There are 500 numbers divisible by 2, from 1 to 1000 There are 333 numbers divisible by 3, from 1 to 1000 ... There are 41 numbers divisible by 24, from 1 to 1000 There are 40 numbers divisible by 25, from 1 to 1000
Hints
- You'll need a loop inside your loop
- You will need a variable inside the first loop to keep track of the total number of evenly-divisible numbers.
Solution
for y in 1..25
number_count = 0
for x in 1..1000
number_count += 1 if x % y == 0
end
puts "There are #{number_count} numbers divisible by #{y}, from 1 to 1000"
end
Other loops
Ruby has some methods belonging to the FixNum class that you can use to start a loop, including times, upto, and downto.
They are often more compact than for, but it boils down to a matter of personal preference. The following loops all do the same thing:
for k in 1..10 do
puts "Number #{k}"
end
10.times do |k|
puts "Number #{k+1}"
# times will start at 0, so on the 10th iteration, k is equal to 9
end
1.upto(10){ |k| puts "Number #{k}"}
A couple of notes:
- The curly braces notation {} is another way of representing do and end. Most Rubyists prefer to use the braces for single-line blocks:
3.times{ |x| puts x} #=> 0 #=> 1 #=> 2
- I'll explain the pipe character notation |k| in the each section below.
The while
Besides for, the most commonly seen loop keyword throughout programming languages is while. This executes a code block while a given condition evaluates to true.
x = 100
while x > 0
x -= 1
puts "This loop will run #{x} more times"
end
#=> This loop will run 99 more times
#=> This loop will run 98 more times
#=> ...
#=> This loop will run 0 more times
Like if, the while loop has a handy inline form. Combined with the -= operator that subtracts from a variable's value and reassigns the new value, we reduce the above loop to one line:
x = 100
puts "This loop will run #{x -= 1} more times" while x > 0
To infinity and...nowhere
So, if while keeps going while some condition is true...what happens when that condition is always true?
In the above example, if you didn't decrement the x variable, your Ruby interpreter would run until your operating system gave out.
Go ahead and try it just so you know what it looks like. You can hit Ctrl-C to break out of the executing script.
Some scripts might have the intention of going on forever, such as one that continually waits for user input before doing something:
while 1
puts "Hello, what's your name?(hit Enter when done)"
user_name = gets.chomp
puts "You will never get out of here, #{user_name}!!\n\n"
end
Rubyists prefer each
Not that you should forget the for loop, as it's used in most of the popular programming languages. But in Ruby, the preferred way of doing a for-type of loop is to instead use the each method.
This will become more useful when we get into collections. But we can use it with the Range (which is essentially a "collection" of integers) class that we're familiar with:
for k in 1..100 do
puts "#{k}. This is not as fun the each construct"
end
(1..100).each do |k|
puts "#{k}. This is Ruby preferred way of doing loops, when possible"
end
(1..100).each{ |k| puts "#{k}. Curly braces make it even shorter"}
Note that there are parentheses around the range, e.g. (1..100).each{ ... as opposed to 1..100.each{ ...
Ruby isn't smart enough to know that the each method in the latter case belongs to the Range of 1..100, and not just to 100 the Fixnum
From here on out, we will be using each and its variations for the majority of our loops. We will hardly ever use for.
Blocks, briefly
Blocks deserve a separate chapter for a fuller explanation. But for our purposes, think of them as anonymous methods. They are snippets of codes that, when provided arguments, will do something with those arguments. But, in the name of brevity, these particular methods aren't worth naming.
So, instead of this:
def doubling_method(num)
puts num * 2
end
for i in 1..3 do
doubling_method(i)
end
#=> 2
#=> 4
#=> 6
We can skip defining the method doubling_method altogether:
1.upto(3) do |i|
puts i * 2
end
The one-liner version:
1.upto(3){ |i| puts i * 2 }
There's much more to blocks but it's enough for now to recognize their notation and function.
The block can be denoted with the do and end construct:
(1..10).each do |num|
puts num
puts "hello"
end
Or you can use curly braces. This form is preferred if the block is a one-liner:
(1..10).each{ |num| puts num}
Exercise: Double for loops
Using for loops, write a script that: prints out the answers to a 9x9 multiplication table.
For example, the answers for a 3x3 multiplication table should be output as:
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
Hints
- You'll need at most two for loops
- One loop should be inside the other
Solution
Here's the easiest, acceptable answer:
for row_num in 1..9
line = ""
for col_num in 1..9
line += "#{row_num * col_num}\t"
end
puts line
end
Note the order of operations here: at the start of the outer for loop, we initialize the variable line to an empty string. Inside the inner for loop, we perform the multiplication and tack the answer onto the end of line.
After the inner loop ends at 9, we escape back into the outer loop which has one final command: output line to screen.
And then we go back to the top of the outer loop which assigns line to an empty string before re-entering the inner loop all over again.
The output:
1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
Exercise: Use each instead
Repeat the same exercise, but with each
Solution
(1..9).each do |row_num|
line = ""
(1..9).each{ |col_num| line += "#{row_num * col_num}\t"}
puts line
end
It should be clear how useful it is to rip through thousands of calculations and operations with a simple loop. In the next chapters, we'll learn about Ruby classes for storing and accessing large datasets with these loops.