8. Week 4 Thursday: While Loops and For Loops
≪ 7. Week 4 Tuesday: Practise with If Statements | Table of Contents | 9. Week 5 Tuesday: Practise with Loops and Strings ≫So far, we’ve learned a lot about coding: we’ve learned how to produce outputs, accept user inputs, store and manipulate variables, and use if statements to produce branching, responsive behaviours. However, this is still a rather limited set of capabilities, and we’re far from capable of utilising C++’s full power.
What if you’re printing out all of the numbers between 1 and 1000, for whatever reason? There’s got to be a better way to do this than just typing cout << 1 << " " << 2 << " " << ....
. Moreover, what if you’re printing all the numbers between 1 and 10000? Or even worse?
While this situation is quite contrived, there are many more situations where you need to repeat code a known or unknown number of times, such as:
- You’re collecting data from an electronic thermometer. You’ve harvested two million data points, but you need to convert the data from Celsius to Fahrenheit.
- Sub-scenario: you’re interested in statistics related to this data, such as the mean, standard deviation, how well it fits a model, etc.
- You’ve scanned a book into your computer, and you need to count how many words are in that book.
- You need to compute a mathematical expression that’s defined using sums, products, or a recursive relationship — examples include computing \(n!\), computing binomial coefficients, computing elements of the Fibonacci sequence, etc.
- A related sub-scenario is if you need to apply Newton’s method to numericall solve a differential equation (or, more commonly, a higher-order numerical solution such as the Runge-Kutta methods).
- You’re sorting your (totally legal) digital library of thousands of movies alphabetically.
There are a billion other scenarios that revolve around repeating instructions some number of times or until some condition is reached. As a mathematician, most of my examples will arise from data analysis and numerical methods, but I hope it’s enough to convince you nonetheless.
We can delineate loops into two separate categories: either you are repeating a set of instructions until something happens, or you are repeating a set of instructions a known number of times. We can describe the first situation as a while
loop, and the syntax is as follows:
The condition needs to be a boolean value, just like in an if statement.
When a C++ program encounters a while loop, it always checks the condition before running the code in the body of the while loop. More specifically, it will first check the condition within the parentheses. If this condition is false, it will skip the entire body of the while loop and continue executing the code following the loop; if the condition is true, it runs the body of the while loop, then repeats the process again.
Notably, it is entirely possible for a while loop not to run at all. If the condition is false the first time a program encounters the while loop, the body of the loop is skipped immediately without being executed. On the flip side, it’s also possible for the condition of the while loop to remain true for the duration of the program. This can cause the program to enter an infinitely repeating set of instructions. This is almost never the expected behaviour and constitutes a logical error.
In fact, while loops can also be used to repeat code for a fixed number of repetitions as follows:
1int repetitions = 0;
2while(repetitions < [number of repetitions]) {
3 // repeated code...
4 repetitions++;
5}
This will keep track of how many times the code has been repeated. It will repeat the code until it’s been repeated an appropriate number of times.
This is bad for two reasons:
- You need two extra lines to keep track of the number of repetitions, and they may be spread out over your code. This makes the code far less manageable.
- The variable name
repetitions
in the above example can never be declared again. You will either have to re-use the same variable several times (and keep track of it throughout all of your code!) or come up with a new variable name for each loop.
The workaround is to use a for loop, which has the following syntax:
This keeps everything in one place so it’s easier to keep track of, especially for when you need to modify your code. You may replace counter
with whatever name you want! Often times, we’ll use the name i
instead of counter
since it’s easier to write.
Very generally speaking, if you want to repeat code a known number of times, you should use a for loop. If you don’t know how many times you’ll need to repeat some code, you should use a while loop.
There are some things to pay attention to regarding for loops, however.
- Be wary of the semicolons! They only go between the three statements within the for loop, but not after the
counter++
. - The variable
counter
is accessible within the loop, but not outside the loop. Once the for loop has finished running, the variablecounter
will cease to exist, and you are allowed to redefine it (or even use it in a new for loop) if you want! - The
int counter = 0;
functions as a mostly regular variable declaration in C++. This means that if you have already defined the variablecounter
before, the code will break. - Like while loops, for loops check the condition before each repetition of the loop.
Common Mistake 1. Off-by-One Errors
Suppose, for whatever reason, that you need to print the string “I like cheese” to the screen five times. You write the code
However, you realise that when you run this code, it prints six lines instead of five! This is because the condition in the for loop is i <= 5
instead of i < 5
; this causes the variable i
to range over the values 0, 1, 2, 3, 4, 5
, i.e. it causes the print statement to repeat 6 times. This is very, very common and easy to overlook. Be very mindful of how many times your for loop repeats and which values your counter variable (in this case i
) is taking.
Remark 2. Unwrapping for loops
You may not need to know this for PIC 10A, but I think it provides some relevant information.
The syntax described above is not the broadest description. In fact, the proper syntax would be
This code is exactly the same code as
This illustrates exactly which order the statements inside the for
loop are executed in. It additionally allows you to write some very strange code that magically works, but I’ll omit that to avoid confusing you.
That was quite a bit of text, so let’s look at an example.
Example 3. Some Sums
Write a C++ program that allows a user to input as many integers as they want until they input a zero. Then, output the sum of the numbers the user input.
Sample run:
INPUT 1 2 3 4 5 6 7 8 9 0
OUTPUT 45
INPUT 3 9 -9 -3 0
OUTPUT 0
INPUT 0
OUTPUT 0
As with most programs, we should start by writing some pseudocode.
- Create a variable called
running_sum
, and set it equal to0
. - Ask the user for an integer input
n
. - If
n
is zero, skip to the last step. - Add
n
to the running sum, then go back to step 2. - Print the value of
running_sum
to the screen.
Here, we don’t know how many times we need to repeat steps 2-4. Thus, we should use a while loop! We know that we should repeat those steps as long as the user has not input a zero, so that should be the condition in the while loop. Here’s the solution in code:
1#include <iostream> // needed for input and output
2
3using namespace std;
4
5int main() {
6 // 1. initialise the running sum
7 int running_sum = 0;
8
9 // 2. ask the user for an input.
10 int n;
11 cin >> n;
12
13 // 3, 4, and repeating 2: add n to the sum and get
14 // a new input, but only as long as the user has not
15 // input a zero.
16 while(n != 0) {
17 running_sum += n; // update running sum
18 cin >> n; // get new input
19 }
20
21 // now that we're out here, the user has input a zero.
22 // 5. print the result.
23 cout << running_sum << endl;
24 return 0;
25}
There’s a few things to make note of:
- We initialised the running sum to zero. Can you see why? This pattern is quite common, and the variable
running_sum
is called an accumulator (the linked source draws on concepts we have yet to learn about). - The above is not quite a one-to-one translation of our pseudocode. In particular, lines 11 and 18 are both repetitions of step 2 in the pseudocode. See the bottom of this page for more about this.
Now try the following problems on your own. Some of these problems can be quite involved. I encourage you to write complete pseudocode before attempting to code a solution. Carefully consider a comprehensive set of test cases to make sure your program works in all cases.
Problem 4. Big and Small
Write a C++ program that accepts a positive integer input n
from the user. Then, take an additional n
integers from the user as input. Afterwards, print out the smallest and largest integers provided by the input.
Sample Run:
INPUT 5
INPUT -5 -3 0 3 5
OUTPUT Smallest: -5
OUTPUT Largest: 5
INPUT 10
INPUT 10 1 9 2 8 3 7 4 6 5
OUTPUT Smallest: 1
OUTPUT Largest: 10
Problem 5. Averaging
Write a C++ program that accepts a positive integer input n
from the user. Then, take an additional n
numbers from the user as input. Finally, print the average of the inputted numbers.
Sample Run:
INPUT 5
INPUT -5 -3 0 3 5
OUTPUT Average: 0.0
INPUT 3
INPUT 1.7 3.9 -2.2
OUTPUT Average: 1.13333
Problem 6. Sum Digits
Write a C++ program that asks a user for an integer input n
. Then, print out the sum of the digits of n
.
Sample Run:
INPUT 38147
OUTPUT 23
INPUT -1193
OUTPUT 14
INPUT 0
OUTPUT 0
Hint
n
. What does n / 10
do, and what does n % 10
do?
Problem 7. Greatest Common Divisor
Write a C++ program that asks the user for two integer inputs, then prints out the greatest common divisor of those two integers.
Sample Run:
INPUT 5 15
OUTPUT 5
INPUT -3 27
OUTPUT 3
INPUT 7 6
OUTPUT 1
Challenge
It’s likely that the algorithm you came up with was moderately inefficient. For instance, when tasked with finding the GCD of 3842734
and 2509273
, it probably scanned about 2.5 million numbers before settling on the GCD. Read about the Euclidean algorithm for efficiently computing the GCD of two numbers, then implement the algorithm yourself.
As a bonus, print out how many code repetitions the Euclidean algorithm needs to determine the GCD.
Remark 8. Do-While Loops
To the best of my knowledge, this is not something you need to know for PIC 10A. However, I think it’s an important type of loop you may encounter or want to use yourself in the future.
As we saw in the example earlier, we had an issue of repeating code. However, there wasn’t much of a way around it — our while loop needs to check the user’s input, but it can’t check the user’s input before they input something! This necessitates a bit of redundancy.
This issue is quite common. Often times, you’ll be accepting input from a source until you receive a special input or you reach the end of a file. However, you know you have to ask for input at least once, and only after that first input do you need to check for inputs.
One way to address this issue is the do-while loop. The syntax is as follows:
Note that there is a semicolon after the while! This will first run the code within the curly braces, then check the condition on the third line. If the condition is false, it just keeps going; if the condition is true, it returns to the start of the do-while loop.
Rewriting the solution to the example problem, we get something a little cleaner:
1#include <iostream> // needed for input and output
2
3using namespace std;
4
5int main() {
6 // 1. initialise the running sum
7 int running_sum = 0;
8
9 // 2-4. get user input, update the sum,
10 // and repeat as necessary.
11 int input;
12 do {
13 cin >> input;
14 running_sum += input;
15 } while(input != 0);
16
17 // 5. print the result.
18 cout << running_sum << endl;
19 return 0;
20}
Although this is more concise, I think it’s considerably more opaque than the above solution. Carefully think through why this code works, and address the following questions:
- Why is it okay to add the input to the running sum no matter what the user’s input is?
- Why did we have to initialise the variable
input
outside of the do-while loop? Try moving line 10 into the do-while loop. What happens? (This happens because of something called variable scope, and we’ll learn more about this later in the quarter.)