Week 06

Looping with While and Do
Version 1


 


Loops Without Limits: While Loops

 

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:

  1. 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:
  1. Get a grade
    1. Prompt for and input a grade
    2. If the grade is outside the range of appropriate values (0<=grade<=100)
      1. Print an error message
      2. Prompt for and input a new grade
      3. 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:

  1. Prompt for and get a and b.
  2. If a and b are equal, we are done. Jump to step 5.
  3. Replace the larger of a and b with a - b.
  4. Return to step 2.
  5. 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);
  }
 
}


Do Loops

  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") );
  }
}

Last modified August 28 for Fall 99 cs5 by fleck@cs.hmc.edu


This page copyright ©1998 by Joshua S. Hodas. It was built with Frontier on a Macintosh . Last rebuilt on Sun, Sep 20, 1998 at 3:56:06 PM.
http://www.cs.hmc.edu/~hodas/courses/cs5/week_06/lecture/lecture.html