| |
|
|
| |
In the last lecture we discussed the for loop which
is designed to allow you to repeat some process some set number
of times. The key, is that you must know in advance
(at least by the time you begin the loop) exactly how many
times you want to execute the code in the body of the loop.
Sometimes, however, you do not know in advance how many times you
wish to repeat a given task.
|
if Is No Good For Error Checks |
For example, suppose you are writing
a grading program and the user is supposed to enter a grade between
0 and 100. You might use the following lines to get the grade:
HMCSupport.out.print("Enter a grade between 0 and 100: ");
grade = HMCSupport.in.nextInt();
But what happens if the user types in 125 for the grade?
We could attempt to guard against this by following up with the
code:
if (grade < 0 || grade > 100) {
HMCSupport.out.print("That's out of range.");
HMCSupport.out.print("Enter a grade between 0 and 100: ");
grade = HMCSupport.in.nextInt();
}
But what if the user is profoundly stupid and types a bad value
again? No matter how many copies of this conditional statement
we use one after the next, the user could outlast us and still break
the program.
|
| While Loops Are Infinitely Patient |
The solution is a while loop. The while
loop is similar to the for loop in that it repeats the statements
in the body of the loop over and over. But, rather than running a
pre-determined number of times, the loop is controlled by an arbitrary
test, like an if statement. The syntax of the while
loop is:
while (test-expression) {
first statement to do as long as test is true;
...
last statement to do as long as test is true;
}
|
The behavior of the while statement begins the same
as an if statement without an else block. If the test
evaluates to false then the code in the body of the
loop is ignored, and execution continues with the code
following the loop. If, on the other hand, the test evaluates
to true then the code in the body of the
loop is executed. Unlike the if statement,
when execution reaches the end of the body of the loop,
the test is evaluated again and the process is repeated until such time
as the test expression evaluates to false.
So, in the last example, if we want to block against an
immensely stupid user, we would follow the initial attempt
to get a grade with the following code:
while (grade < 0 || grade > 100) {
HMCSupport.out.print("That's out of range.");
HMCSupport.out.print("Enter a grade between 0 and 100: ");
grade = HMCSupport.in.nextInt();
}
If the user types a good value in response to the
initial prompt, the test will evaluate to false
and the code in the body of the loop will be ignored.
If they type an invalid grade, the test will evaluate
to true and the loop will be entered.
No matter how many times they type a bad number, the
loop will patiently repeat. Eventually, the user will either
fall asleep or they'll type a good grade. At that
point, the next time the test is evaluated, it will come
out to false and execution will continue
below the loop.
|
| An Improved Grade Averager |
We can use this idea in our for-loop-based program from
the last lecture that computed the
average of 10 grades the user entered. This version ensures that
the user enters grades in the proper range. If they enter a
grade out of range, it will repeatedly correct them and prompt
for a new value until they give a proper value. Only then will
it proceed to the next grade.
In terms of the top-down-design of the program, the line insode the loop
that previously said:
- Get a grade
which we previously thought of as being translated by a simple prompt and
get (and therefore did not expand further in the design) now expands as:
- Get a grade
- Prompt for and input a grade
- If the grade is outside the range of appropriate values (0<=grade<=100)
- Print an error message
- Prompt for and input a new grade
- Retun to test above
This change leads to the following version of the program:
| Click Here To Run This Program On Its Own Page |
// Program: AverageGrades2
// Author: Joshua S. Hodas
// Date: October 8, 1996
// Purpose: To demonstrate the use of while loops
// for error checking
import HMC.HMCSupport;
class AverageGrades2 {
public static void main(String args[]) {
float grade, sum = 0, average;
HMCSupport.out.println("This program averages 10 grades.");
for (int gradeNum = 1 ; gradeNum <= 10 ; gradeNum++) {
HMCSupport.out.print("Please enter grade #" + gradeNum
+ ": ");
grade = HMCSupport.in.nextInt();
while (grade < 0 || grade >100) { // Loop if bad grade
HMCSupport.out.print("Grades must be between 0 and "
+ " 100, re-enter: ");
grade = HMCSupport.in.nextInt();
}
sum = sum + grade;
}
average = sum/ 10;
HMCSupport.out.println("The average of the grades is "
+ average);
}
}
|
|
| Be Sure Your Loop Test Makes Sense |
Since we can put any boolean expression we want into the
loop test, it is vital that the test make sense, and correspond
to what we want the loop to accomplish. Otherwise the test might always evaluate to false at the beginning and
the loop would never be entered, or the test might always evaluate to true and the
loop would repeat forever and the program never proceed beyond the loop.
|
|
Determining that the test "makes sense" generally involves focusing on any variables used in the
test expression. Things to look out for:
- The variables must be initialized to reasonable values before we evaluate the test the first time.
For example, in the last program, we made sure to put an initial value for
grade (input from the user) into that variable before we got to the point of
testing that variable's value.
Fortunately, the Java compiler will largely take care of watching out for this situation for us.
As we have said earlier, it will not
accept an expression that attempts to evaluate the value of a variable if it is not convinvced that
the variable will have been initialized by that point. For example, if we had forgotten to
do the intial input into grade, then when it got to the test expression, the
compiler would report:
AverageGrades2.java:22: Variable grade may not
have been initialized.
while (grade < 0 || grade > 100) { // Loop if bad grade
^
- The test must have at least some chance of coming out to
true the first time.
If the test can never evaluate to true at the beginning, the loop is pointless since it will never be entered. Ensuring this involves checking the pre-condition
of the loop. That is, what could be true of the values of the variables in the test before
we arrive at the loop?
In this example, the precondition is that the variable grade could have
any value (since it is typed by the user). So, there is some chance that the value is out
of range and that the test could therefore evaluate to true.
- If the test can evaluate to
true at the beginning, then something done inside the body of the loop must make it possible for the test to eventually evaluate to false.
Otherwise, once we enter the loop we will loop forever. This involves examining what is done
in the body of the loop to the variables used in the test expression and how that relates to the form of the test expression.
In this example, the test requires that the loop terminate if the value of the variable
grade is ever in the proper range, 0-100. Since each time we go through the loop the
variable is given a new value typed by the user, there is some chance that the
value will eventually be in the right range, and cause the test to evaluate to false
and the loop to exit.
- What will be true at the beginning of the code immediately below the loop when the test evaluates to
false and the loop is exited (or skipped over entirely, if the test evaluates to false the first time)?
Does the loop test insure that this post-condition is exactly what we want it to be?
In this example, we want the post-condition to be that the variable grade can only
have a valid value in the range 0 to 100. It is clear that the loop test expression will guarantee
that this post-condition holds by the time we reach the code below the while loop.
While we have discussed these design ideas in terms of while loops, they really apply
to using any loop construct in any language. And, while they may seem obvious, pretty much
every loop-related logical error (one that causes your program to run incorrectly,
as opposed to a syntax error which keeps it from compiling) can be seen as
violating one of these design criteria. At this level of programming it is a good idea to think
of this as a checklist that you review each time you write a loop.
|
| While Loops For Open Ended Compu- tation |
In the last example we used a while loop in response to the
unpredictability of a user. We chose a while loop because there was no way to predict in
advance how many times the user might enter bad input.
A while loop can also be used in computational contexts
in which the number of iterations (a big
word for passes made through a loop) can't be determined in
advance. To compute the factorial of a number n, for example,
we know that exactly n passes through a loop will be necessary,
and that a for loop is therefore a good choice.
But in other loop-based computations the number of iterations
needed may not be known in advance.
|
| Greatest Common Divisor |
For example, for two natural numbers (i.e., integers greater
than zero) a and b, a variant of
Euclid's method says that if a
is greater than b, then the greatest common
divisor of a and b is the same as the
greatest common divisor of a - b and b.
(If b is larger, the relationship is flipped.)
The problem is, we do not know how many iterations of the loop
will be needed (that is, how many times we will need to subtract
one number from the other) before the two numbers converge to the
GCD.
The outline of a program to compute the greatest common divisor
could be given as:
- Prompt for and get a and b.
- If a and b are equal, we are done. Jump to step 5.
- Replace the larger of a and b with a - b.
- Return to step 2.
- Print the value of a (or b) which is now the
greatest common divisor of the original numbers.
This corresponds to the following program:
| Click Here To Run This Program On Its Own Page |
// Program: EuclidsMethod
// Author: Joshua S. Hodas
// Date: October 8, 1996
// Purpose: To demonstrate the use While Loops to compute
import HMC.HMCSupport;
class EuclidsMethod {
public static void main(String args[]) {
int a, b;
HMCSupport.out.println("This program computes the "
+ "GCD of two numbers.");
HMCSupport.out.print("Enter the two numbers: ");
a = HMCSupport.in.nextInt();
b = HMCSupport.in.nextInt();
while ( a != b ) {
if ( a > b) { // replace the larger number with
// the difference of the two numbers
a = a - b;
}
else {
b = b - a;
}
}
HMCSupport.out.println("The greatest common divisor is "
+ a);
}
}
|
Notice that this program has one flaw. This variant of Euclid's Method
only works for positive numbers. The program does not guarantee the condition
that the two numbers be greater than zero. Thus, it is possible for the
loop to go on endlessly when that pre-condition is not satisfied. In turn,
the post-condition that both values be the GCD of the original pair of values
will never hold.
If we wanted to enforce the pre-condition, we would need to add an input
verification loop, like the one used for the grades above, to the beginning of the
program, and only proceed to the computational loop once we had positive numbers
for both inputs.
|
| Sentinel Variables |
Sometimes the test expression in a while loop may grow very
complex. At those times it may make it easier to read the
loop if the test is broken apart and boolean variables
(with meaningful names) are used to stand in for the
parts of the test. Such variables are called sentinel variables
because they stand watch over the execution of the loop.
In our last example the test was not too complex, but
we will use it as an example of using sentinel variables,
nevertheless, so that you can see how it relates to the last
version. In that program the computation was finished when the
two numbers were equal. Therefore, we will create a sentinel
variable named finished that begins with the value
false. We will set the sentinel to true
only when the two numbers converge:
| Click Here To Run This Program On Its Own Page |
// Program: EuclidsSentinel
// Author: Joshua S. Hodas
// Date: October 8, 1996
// Purpose: To demonstrate the use of sentinel variables
import HMC.HMCSupport;
class EuclidsSentinel {
public static void main(String args[]) {
int a, b;
boolean finished;
HMCSupport.out.println("This program computes the "
+ "GCD of two numbers.");
finished = false;
HMCSupport.out.print("Enter the two numbers: ");
a = HMCSupport.in.nextInt();
b = HMCSupport.in.nextInt();
while (!finished) { // That is, "while not finished..."
if ( a == b ) {
finished = true;
}
else if ( a > b) { // replace the larger number with
// the difference of the two numbers
a = a - b;
}
else {
b = b - a;
}
}
HMCSupport.out.println("The greatest common divisor is "
+ a);
}
}
|
|
|
| |
With while loops, the test comes at the beginning of the
loop and if it is false the first time it is encountered
the body of the loop is never executed. In some cases, you know for sure
that you want to execute the body of the loop at least once, and it
would be easier to delay the test until the end of the loop. This is
what the do loop does. The syntax of this
statement is:
do {
statements to do once and then when test is true;
...
} while (test-expression)
|
For example, with a lot of the programs we write, it would be
nice at the end to offer the user a chance to repeat the program, in order
to save them the effort of relaunching the program from the
unix prompt after it has finished. Presumably, though, if they
run your program, they really want to use it, so it would
be strange to ask them at the beginning if they want to run it
again (as you might if you wrapped the whole program in
a while loop).
A better solution is to wrap the program in a do
loop, and delay the question and the test that goes with it
until the end. So, for example, we can modify the Euclid's Method
program to offer an opportunity to run it again:
| Click Here To Run This Program On Its Own Page |
// Program: EuclidsDoLoop
// Author: Joshua S. Hodas
// Date: October 8, 1996
// Purpose: To demonstrate the use of do loops
import HMC.HMCSupport;
class EuclidsDoLoop {
public static void main(String args[]) {
int a, b;
String again;
HMCSupport.out.println("This program computes the "
+ "GCD of two numbers.");
do {
HMCSupport.out.print("Enter the two numbers: ");
a = HMCSupport.in.nextInt();
b = HMCSupport.in.nextInt();
while (a != b) {
if ( a > b) { // replace the larger number with
// the difference of the two numbers
a = a - b;
}
else {
b = b - a;
}
}
HMCSupport.out.println("The greatest common divisor is "
+ a);
HMCSupport.out.println();
HMCSupport.out.print("Would you like to try another"
+ " pair of numbers (y/n)?");
again = HMCSupport.in.nextWord();
} while ( again.equals("y") || again.equals("Y") );
}
}
|
|