While Loops
How does a program repeat work without us repeating the code?
By the end of this lecture you will be able to
- Write a
whileloop with its three parts: initialise, test and update - Trace the iterations of a loop in a table and with the debugger
- Use a counter and a running sum to calculate totals and averages
- Spot and fix infinite loops and off-by-one errors
- Read input until a sentinel value and validate input with
do-while - Solve digit problems, a guessing game and factorial with loops
In this lecture
- Printing 1 to 100 by hand means writing 100 lines
- Every while loop has three parts: initialise, test and update
- A trace table records what happens in each iteration
- The debugger shows you the loop turning, one line at a time
- A counter grows by one, a running sum adds each new value
- An average is the running sum divided by the counter, cast to double
- A loop whose test never becomes false never ends, and Stop ends it
- Off-by-one errors: the loop runs one time too many or too few
- A sentinel value tells the loop that the input is over
- do-while runs the body once first, then checks the test
- do-while suits input validation: ask, then ask again if the answer is wrong
% 10gives the last digit and/ 10removes it- The same idea reverses a number and counts its digits
- A guessing game combines the loop, the condition and a counter
- Factorial starts at 1, because multiplying by 0 erases everything
- Review questions
Printing 1 to 100 by hand means writing 100 lines
What we know so far runs each line at most once. To print 1 to 100 we would need 100 nearly identical println statements, and one small change would mean editing 100 lines. A loop solves this: we write the work once and say when it stops.
Five lines instead of a hundred
Run the program and look at the output, then change 100 to 20 and run it again.
public class OneToHundred {
public static void main(String[] args) {
int number = 1;
while (number <= 100) {
System.out.println(number);
number++;
}
}
}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
Every while loop has three parts: initialise, test and update
Gives the loop variable its first value before the loop starts. It runs once.
int count = 1;Checked before every iteration: if true the body runs, if false the loop ends.
while (count <= 3)Changes the loop variable inside the body, moving the test closer to false.
count++;All three parts in one program
Predict the output before running. Then change the update to count += 2 and explain to a classmate why the output changed.
public class CountToThree {
public static void main(String[] args) {
int count = 1; // initialise
while (count <= 3) { // test
System.out.println(count);
count++; // update
}
System.out.println("Done");
}
}1 2 3 Done
The test is checked before the first iteration too. What does this print?
public class NeverRuns {
public static void main(String[] args) {
int count = 10;
while (count < 5) {
System.out.println(count);
count++;
}
System.out.println("Done");
}
}Show the output
Done
Why? The test 10 < 5 is false from the start, so the body never runs and the program goes straight to the line after the loop.
A test must be true or false
We wanted to print 0, 1 and 2, but the compiler rejects the test. Fix it.
public class TestType {
public static void main(String[] args) {
int count = 0;
while (count = 3) {
System.out.println(count);
count++;
}
}
}incompatible types: int cannot be converted to booleanShow the fix
public class TestType {
public static void main(String[] args) {
int count = 0;
while (count < 3) {
System.out.println(count);
count++;
}
}
}0 1 2
Why? The = sign is assignment, so count = 3 has the value 3 rather than true or false, and a loop needs a boolean test such as count < 3.
A trace table records what happens in each iteration
Before we let the computer work, we do its work on paper: a column for each variable, a column for the test and a row for each iteration. This is the CountToThree program from the last section:
| Iteration | count at the test | count <= 3 | Prints | count after the update |
|---|---|---|---|---|
1 | 1 | true | 1 | 2 |
2 | 2 | true | 2 | 3 |
3 | 3 | true | 3 | 4 |
| Ends | 4 | false | nothing | unchanged |
Fill in the trace table. What does this print?
public class TraceByThree {
public static void main(String[] args) {
int i = 2;
while (i < 12) {
System.out.println(i);
i += 3;
}
System.out.println("End: " + i);
}
}Show the output
2 5 8 11 End: 14
Why? i grows by 3 each iteration: 2, 5, 8, 11. After printing 11 it becomes 14, so the test is false and the last line prints.
The debugger shows you the loop turning, one line at a time
- Press "Debug line by line"The program stops at the first line and highlights it. The highlighted line has not run yet.
- Press "Next line" or F8Before each press, say out loud which line will run next.
- Watch the boxThe box
countappears, and its value changes at the update line. - Watch the jumpAfter the last line of the body the highlight jumps back to the
whileline. This is where the looping really happens.
Trace the countdown
Trace the program to its end and count how many times the highlight stops on the while line. Then change 3 to 5 and predict the new number before you trace it.
public class Countdown {
public static void main(String[] args) {
int count = 3;
while (count > 0) {
System.out.println(count);
count--;
}
System.out.println("Go!");
}
}3 2 1 Go!
A table on paper, then the debugger
Fill in a table for total and steps on paper first, then trace the program and compare each row with the boxes. Where does your table differ from the debugger?
public class Doubling {
public static void main(String[] args) {
int total = 1;
int steps = 0;
while (total < 50) {
total = total * 2;
steps++;
}
System.out.println("total = " + total);
System.out.println("steps = " + steps);
}
}total = 64 steps = 6
A loop runs its body 5 times and then ends normally. How many times was its test checked?
false and ended the loop.A counter grows by one, a running sum adds each new value
These boxes show the program below as iteration four begins: we have added 1, 2 and 3, so the sum is 6. The sum starts at 0, because adding 0 changes nothing, and it builds up inside the loop with sum += number.
Add the numbers from 1 to 10
Trace the program and watch sum in each iteration. Then move int sum = 0; inside the loop and predict what happens: the compiler rejects the last line, because sum is no longer known outside the loop.
public class RunningSum {
public static void main(String[] args) {
int number = 1;
int sum = 0;
while (number <= 10) {
sum += number;
number++;
}
System.out.println("Sum = " + sum);
}
}Sum = 55
A counter that does not grow every time: what does this print?
public class CountEvens {
public static void main(String[] args) {
int number = 1;
int evens = 0;
while (number <= 9) {
if (number % 2 == 0) {
evens++;
}
number++;
}
System.out.println(evens);
}
}Show the output
4
Why? The loop turns 9 times, but evens grows only at 2, 4, 6 and 8. The number++ sits outside the if so that it changes every iteration.
Sum of the numbers up to n
Read a whole number n, then print the sum of the numbers from 1 to n. Print nothing else. If n is 0 the answer is 0.
import java.util.Scanner;
public class SumToN {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
// Add the numbers from 1 to n, then print the sum
}
}5
15
Hint 1
You need two variables before the loop: a counter starting at 1 and a sum starting at 0.
Hint 2
The test is counter <= n, so the body never runs when n is 0.
Solution
import java.util.Scanner;
public class SumToN {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int counter = 1;
int sum = 0;
while (counter <= n) {
sum += counter;
counter++;
}
System.out.println(sum);
}
}An average is the running sum divided by the counter, cast to double
The average of three grades
This lab reads fixed input: 80, 90 and 75. Trace the program and watch sum and count together. Then change count < 3 to count < 2 and predict the new average.
import java.util.Scanner;
public class AverageThree {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int count = 0;
int sum = 0;
while (count < 3) {
int grade = input.nextInt();
sum += grade;
count++;
}
double average = (double) sum / count;
System.out.println("Sum: " + sum);
System.out.println("Average: " + average);
}
}Sum: 245 Average: 81.66666666666667
Remember integer division. What does each line print?
public class AverageCast {
public static void main(String[] args) {
int sum = 7;
int count = 2;
double wrong = sum / count;
double right = (double) sum / count;
System.out.println(wrong);
System.out.println(right);
}
}Show the output
3.0 3.5
Why? In sum / count both sides are int, so the decimal is dropped first and 3 is then stored as 3.0. The (double) cast happens before the division, so the decimal survives.
A loop whose test never becomes false never ends, and Stop ends it
If you forget the update line, count stays the same and the test stays true forever. This is an infinite loop, and it happens to every programmer. Do not fear it: on this site a Stop button appears while a program runs, so the loop ends and the page stays alive.
int count = 1;
while (count <= 5) {
System.out.println(count);
}Write an infinite loop on purpose
This program ends as it stands. Type the fragment above inside main in place of its current lines, press Run, watch the numbers pour out, then press Stop. Afterwards press Reset to get the original program back.
public class LoopPlayground {
public static void main(String[] args) {
int count = 1;
while (count <= 5) {
System.out.println(count);
count++;
}
}
}1 2 3 4 5
A line the program can never reach
The compiler sometimes spots an infinite loop by itself. Fix the program so it prints Hi three times and then Done.
public class Unreachable {
public static void main(String[] args) {
while (true) {
System.out.println("Hi");
}
System.out.println("Done");
}
}unreachable statementShow the fix
public class Unreachable {
public static void main(String[] args) {
int count = 0;
while (count < 3) {
System.out.println("Hi");
count++;
}
System.out.println("Done");
}
}Hi Hi Hi Done
Why? The test true never changes, so the program never leaves the loop, which is why the compiler says the line after it is unreachable. The problem is the test, not the line the message points at.
Off-by-one errors: the loop runs one time too many or too few
This is the most famous loop mistake: the program runs and the compiler has no complaint, but the number of iterations is wrong by one. The cause is usually < instead of <=, or a wrong starting value.
| Start | Test | Values in the body | Iterations |
|---|---|---|---|
i = 1 | i <= 5 | 1 2 3 4 5 | 5 |
i = 1 | i < 5 | 1 2 3 4 | 4 |
i = 0 | i < 5 | 0 1 2 3 4 | 5 |
i = 0 | i <= 5 | 0 1 2 3 4 5 | 6 |
The program runs, but it is wrong
The goal is the sum of the numbers from 1 to 5, which is 15, but the program prints 10. Trace it, find the missing iteration, then fix the test and check that the output becomes 15.
public class OffByOne {
public static void main(String[] args) {
int i = 1;
int sum = 0;
while (i < 5) {
sum += i;
i++;
}
System.out.println(sum);
}
}10
How many times does the body of this loop run?
int i = 0;
while (i <= 10) {
i += 2;
}
i in the body are 0, 2, 4, 6, 8 and 10, which is six values. Do not forget 0, nor 10, because the test uses <=.int i = 1;
while (i <= 5);
{
i++;
}main in the lab above, in place of its current lines; the program prints nothing, and Stop is the way out.A sentinel value tells the loop that the input is over
We may not know in advance how many numbers the user will enter. The fix: agree on a special value that cannot be real data, such as -1, meaning "I am finished". This is a sentinel value.
Type it yourself
Run the program, enter a few numbers, then enter -1. Try entering -1 first: what does it print?
import java.util.Scanner;
public class SentinelSum {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int sum = 0;
System.out.print("Number (-1 to stop): ");
int number = input.nextInt();
while (number != -1) {
sum += number;
System.out.print("Number (-1 to stop): ");
number = input.nextInt();
}
System.out.println("Sum: " + sum);
}
}The input here is 4, 10, 6, then -1. Does -1 join the sum or the count?
import java.util.Scanner;
public class SentinelCount {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int sum = 0;
int count = 0;
int number = input.nextInt();
while (number != -1) {
sum += number;
count++;
number = input.nextInt();
}
System.out.println(count + " numbers, sum " + sum);
}
}Show the output
3 numbers, sum 20
Why? When -1 is read the test runs next, so the loop ends before the value reaches sum or count. That is why the read is the last line of the body.
Average until the sentinel
Read whole numbers until -1, without printing any prompts. Print Count: and how many there were, then on the next line Average: and their average as a double. If the first input is -1, print only No numbers.
import java.util.Scanner;
public class SentinelAverage {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// Read until -1, keeping a sum and a count
}
}80 90 70 -1
Count: 3 Average: 80.0
Hint 1
Start from the pattern in the SentinelCount program above.
Hint 2
After the loop use if (count == 0) so that you never divide by zero.
Solution
import java.util.Scanner;
public class SentinelAverage {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int sum = 0;
int count = 0;
int number = input.nextInt();
while (number != -1) {
sum += number;
count++;
number = input.nextInt();
}
if (count == 0) {
System.out.println("No numbers");
} else {
double average = (double) sum / count;
System.out.println("Count: " + count);
System.out.println("Average: " + average);
}
}
}do-while runs the body once first, then checks the test
while: test, then runThe body may never run.
while (test) {
body;
}do-while: run, then testThe body runs at least once. Note the semicolon at the end.
do {
body;
} while (test);The same test and the same start. What does this print?
public class WhileVsDo {
public static void main(String[] args) {
int a = 10;
while (a < 5) {
System.out.println("while " + a);
a++;
}
int b = 10;
do {
System.out.println("do " + b);
b++;
} while (b < 5);
}
}Show the output
do 10
Why? The test is false in both cases. But while tests first and prints nothing, while do-while runs the body once, then tests and stops.
Trace the difference
Trace the program and notice that the highlight enters the body directly and that the while line comes at the bottom. Then change the start to n = 100 and predict the output.
public class DoWhileTrace {
public static void main(String[] args) {
int n = 1;
do {
System.out.println(n);
n = n * 3;
} while (n < 20);
}
}1 3 9
A variable the test cannot see
We declared n inside the body, and the while line is outside the braces. Fix the program so it prints 3, 2, then 1.
public class DoScope {
public static void main(String[] args) {
do {
int n = 3;
System.out.println(n);
n--;
} while (n > 0);
}
}cannot find symbolShow the fix
public class DoScope {
public static void main(String[] args) {
int n = 3;
do {
System.out.println(n);
n--;
} while (n > 0);
}
}3 2 1
Why? A variable declared inside { } does not exist outside them, and the while line of a do-while is outside them. Moving the declaration before do also fixes a second bug: inside the body, n would go back to 3 on every iteration.
do-while suits input validation: ask, then ask again if the answer is wrong
We must ask the user at least once, and only after the answer do we know whether to ask again. That is exactly how do-while behaves. The test describes the rejected answer, so we keep asking while it is wrong.
Try to fool the program
Run the program and enter 150, then -3, then a valid grade. Try the limits 0 and 100 too. Then swap || for && and predict the result: if the program now accepts everything, why?
import java.util.Scanner;
public class ValidateGrade {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int grade;
do {
System.out.print("Grade (0-100): ");
grade = input.nextInt();
} while (grade < 0 || grade > 100);
System.out.println("Accepted: " + grade);
}
}We want to keep asking until the user enters a positive number. What is the right test in while?
A rating from 1 to 5
Print Rating (1-5): using print and read a number, repeating until the number is between 1 and 5. Then print Thanks, you chose followed by the number. Note: what you type does not appear in the test output, so the prompts run together.
import java.util.Scanner;
public class ValidRating {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int rating;
// Ask with a do-while loop until the rating is valid
rating = 0;
System.out.println("Thanks, you chose " + rating);
}
}9 0 4
Rating (1-5): Rating (1-5): Rating (1-5): Thanks, you chose 4
Hint 1
Delete the rating = 0; line and write the loop in its place.
Hint 2
The test describes rejection: rating < 1 || rating > 5.
Solution
import java.util.Scanner;
public class ValidRating {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int rating;
do {
System.out.print("Rating (1-5): ");
rating = input.nextInt();
} while (rating < 1 || rating > 5);
System.out.println("Thanks, you chose " + rating);
}
}% 10 gives the last digit and / 10 removes it
These two operators take any number apart: take the last digit, remove it and repeat until the number is 0. This is the trace table for 472:
| Iteration | n at the test | n % 10 | sum after adding | n after n / 10 |
|---|---|---|---|---|
1 | 472 | 2 | 2 | 47 |
2 | 47 | 7 | 9 | 4 |
3 | 4 | 4 | 13 | 0 |
| Ends | 0 | not computed | 13 | 0 |
Sum of the digits
Trace the program and compare each iteration with the table above. Then change the number to 9075 and write its table before running.
public class SumOfDigits {
public static void main(String[] args) {
int n = 472;
int sum = 0;
while (n > 0) {
int digit = n % 10;
sum += digit;
n = n / 10;
}
System.out.println(sum);
}
}13
How many times does the digit 7 appear in the number?
public class CountSevens {
public static void main(String[] args) {
int n = 7170;
int sevens = 0;
while (n > 0) {
if (n % 10 == 7) {
sevens++;
}
n = n / 10;
}
System.out.println(sevens);
}
}Show the output
2
Why? The loop reads digits from the right: 0, 7, 1, 7, so the counter grows twice.
The same idea reverses a number and counts its digits
Reverse the number
The statement reversed = reversed * 10 + digit shifts the digits left and puts the new one in the units place. Trace the program and watch the box reversed: 3, then 32, then 321. Then try 120 and explain the output.
public class ReverseNumber {
public static void main(String[] args) {
int n = 123;
int reversed = 0;
while (n > 0) {
int digit = n % 10;
reversed = reversed * 10 + digit;
n = n / 10;
}
System.out.println(reversed);
}
}321
Count the digits
Read a non-negative whole number, then print how many digits it has. Print nothing else. Note that the number 0 has one digit.
import java.util.Scanner;
public class CountDigits {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
// Count the digits of n, then print the count
}
}4096
4
Hint 1
In each iteration remove a digit with n = n / 10 and grow the counter.
Hint 2
Use do-while with the test n > 0 so that the zero case works.
Solution
import java.util.Scanner;
public class CountDigits {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int count = 0;
do {
count++;
n = n / 10;
} while (n > 0);
System.out.println(count);
}
}A guessing game combines the loop, the condition and a counter
The secret here is a fixed number in the code, so we can focus on the loop logic and test it. We do not know the number of attempts in advance, so the loop continues while the answer is wrong.
Play
Run the program and guess the number, which is between 1 and 100. Then change the secret and play with a classmate: what is the fewest attempts that guarantees a win if you always start from the middle?
import java.util.Scanner;
public class GuessGame {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int secret = 42;
int guess;
do {
System.out.print("Your guess: ");
guess = input.nextInt();
if (guess < secret) {
System.out.println("Too low");
} else if (guess > secret) {
System.out.println("Too high");
}
} while (guess != secret);
System.out.println("Correct!");
}
}Count the attempts
The secret is 7. Read guesses without printing prompts: print Too low or Too high after each wrong guess. On a correct guess, print Correct! Attempts: followed by the number of attempts.
import java.util.Scanner;
public class GuessAttempts {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int secret = 7;
// Keep reading guesses, and count them
}
}3 9 7
Too low Too high Correct! Attempts: 3
Hint 1
Grow the attempts counter in every iteration, whether the guess is right or wrong.
Hint 2
Print the winning message after the loop, not inside it.
Solution
import java.util.Scanner;
public class GuessAttempts {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int secret = 7;
int attempts = 0;
int guess;
do {
guess = input.nextInt();
attempts++;
if (guess < secret) {
System.out.println("Too low");
} else if (guess > secret) {
System.out.println("Too high");
}
} while (guess != secret);
System.out.println("Correct! Attempts: " + attempts);
}
}Factorial starts at 1, because multiplying by 0 erases everything
The factorial 5! means 5 × 4 × 3 × 2 × 1 = 120, and 0! is 1 by definition. The pattern is the running sum again, multiplying instead of adding and starting from 1 instead of 0.
We started the result at 0, as we do for a sum. What does this print?
public class ZeroStart {
public static void main(String[] args) {
int n = 4;
int result = 0;
while (n > 0) {
result = result * n;
n--;
}
System.out.println(result);
}
}Show the output
0
Why? Anything multiplied by 0 is 0, so result stays zero in every iteration. Start a product at 1 and a sum at 0.
The box fills up again
Run the program: the output is wrong, because 13! is larger than the biggest int. Change the type of result to long, run it again and compare.
public class FactorialOverflow {
public static void main(String[] args) {
int n = 13;
int result = 1;
while (n > 0) {
result = result * n;
n--;
}
System.out.println(result);
}
}1932053504
Calculate the factorial
Read a whole number n between 0 and 20, then print its factorial. Use the type long. Print nothing else.
import java.util.Scanner;
public class FactorialN {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
// Multiply the result by n, n - 1, ... 1
}
}5
120
Hint 1
Start with long result = 1;.
Hint 2
The loop while (n > 0) does not run at 0, so the result stays 1 as we want.
Solution
import java.util.Scanner;
public class FactorialN {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();
long result = 1;
while (n > 0) {
result = result * n;
n--;
}
System.out.println(result);
}
}Review questions
What happens if you forget the update part of a while loop?
true forever if it started true. The compiler usually cannot detect this.When do you prefer do-while over while?
do-while checks the test after the body, so it suits asking for input and then checking it.Why do we choose -1 as the sentinel in a list of grades?
Trace on paper, then check with the debugger. What does this print?
public class ReviewTrace {
public static void main(String[] args) {
int x = 20;
int steps = 0;
while (x > 1) {
x = x / 2;
steps++;
}
System.out.println(x + " " + steps);
}
}Show the output
1 4
Why? x goes 20, 10, 5, 2, 1, with the decimal dropped in 5 / 2. That is four iterations, and at 1 the test is false.