CSE 283 Introduction to Object Oriented Design

Barbara Nostrand, Ph.D.


CSE 283 Labs
Welcome to the site for CSE 283 Labs!

 

Lab 7-8: Sequential Algorithms

Prev | Next | Lab 7-8

Introduction

One of the easiest ways to make a program user-friendly is to make it menu-driven.  That is, rather than prompting the user in some vague sort of way,  we present them with a menu of the choices available to them.  Then all the user has to do is look at the menu and choose one of the choices.  Since the menu always tells the user what their options are,  the user needs no special knowledge,  making such a program easy to use. 

For example,  a simple 4-function calculator program might prompt the user by providing the following menu:

   Please enter:
      + to add two numbers;
      - to subtract two numbers;
      * to multiple two numbers; or
      / to divide two numbers.
   -->

Thanks to the menu,  a user knows exactly what they are to enter,  rather than having to guess. 

Today's exercise is to complete such a program,  and modify it to operate on Fraction values instead of double values.  At the same time you will learn about some of the Java control structures (statements that control the flow of execution through your program). 

 

Getting Started

Create a Calculate project in which to store today's work.  Then save a copy of Calculate.java and add it to your project.  Also add the packages ann and hoj to your project.  Open the file and personalize its opening documentation.  Then take a few moments to study its structure.  Note the use of Assertion.check() to check the preconditions of our program.  Make sure you understand the purpose of each statement in Calculate.java before you continue. 

Ignoring its error-checking code, Calculate.java prescribes the following behavior:

   Our program should display on the screen a greeting, followed 
   by a menu of permissible operations.  It should then read a 
   user-specified operation from the keyboard.  It should then 
   prompt the user for the (two) operands for that operation,
   and then read those operands from the keyboard.  It should 
   then compute the result of applying the user-specified operation
   to the two operands.  It should conclude by displaying that
   result on the screen, with appropriate labeling.

Except for the portion given in bold,  all of the above capabilities are either pred-defined in Java or naturally belong to the Fraction class.  Since there is no predefined Java capability to directly perform the operation in bold,  we will write method apply() to do so.  Since this method will be closely tied to our calculator problem (and thus not particularly reusable), p we will store it in Calculate.java,  rather than in a separately compiled module.

 

Method Design

As usual, we use object-centered design to develop this method.

Behavior. From a "high level", our method must apply operation to op1 and op2 and return the result. If we break this task down into its "low level" details, we can describe the needed behavior as follows:

   Our method should receive from its caller an operation,
   and two operands.  If the operation is '+, our method 
   should return the sum of the two operands.  If the operation 
   is '-', our method should return their difference. If the 
   operation is '*', our method should return their product 
   of the two operands.  If the operation is '/', our method 
   should return their quotient.

Objects. From this behavioral description, we can identify the following objects:

Description

Type

Kind

Movement

Name

The operation

char

varying

received

operation

One of the operands

Fraction

varying

received

op1

The other operand

Fraction

varying

received

op2

The sum of the operands

Fraction

varying

local

op1.add(op2)

The difference of the operands

Fraction

varying

local

op1.sub(op2)

The product of the operands

Fraction

varying

local

op1.mult(op2)

The quotient of the operands

Fraction

varying

local

op1.div(op2)

From this list, we can specify the behavior of our method as follows:

   Receive: operation, a char;
            op1 and op2, two Fraction values.
   Return: the (Fraction) result of applying operation 
            to op1 and op2.

Using this specification, add a stub for a method named apply() below the main function in Calculate.java.

Method Operations From our behavioral description, we have these operations:

Description

Defined?

Name

Module?

1

Receive operation (a char)

yes

method call
mechanism

built-in

2

Receive op1 (a Fraction)

no

method call
mechanism

??

3

Receive op2 (a Fraction)

no

method call
mechanism

??

4

Return the sum of op1 and op2 (a Fraction)

no

add

Fraction

5

Return the difference of op1 and op2 (a Fraction)

no

sub

Fraction

6

Return the product of op1 and op2 (a Fraction)

yes

mult

Fraction

7

Return the quotient of op1 and op2 (a Fraction)

no

div

Fraction

8

Do exactly one of 4-7, depending on operation

yes

??

built-in

As indicated,  you will need to add new methods to the Fraction class which you originally developed in Lab 6.  The natural order of operation for these methods is for the object itself to serve as the left-hand operand and for the parameter value to serve as the right-hand operand.  This means that:  op1.sub(op2) will return the value op1-op2.  Operation #8 is different from the others,  in that it requires selective behavior.  One way to elicit selective behavior is by using the Java if statement.  Such behavior can also be elicited by using the Java switch statement.  We will compare and contrast these statements in the rest of this exercise. 

Method Algorithm. We can organize these operations into the following algorithm:

   0. Receive operation, op1 and op2.
   1. If operation is '+':
         Return op1 + op2.
      Otherwise, if operation is '-':
         Return op1 - op2.
      Otherwise, if operation is '*':
         Return op1 * op2.
      Otherwise, if operation is '/':
         Return op1 / op2.
      End if.

Here, we use a pseudo code form of the if statement that has multiple branches. The trick is to see how to code such a form in Java.

 

Coding 1: Using the if Statement

If you have correctly declared parameters for operation, op1 and op2 in your prototype and stub, step 0 of our algorithm should be taken care of. That just leaves step 1. As we suggested earlier, each of the "Return ..." parts of step 1 can be performed using a return statement that returns an appropriate expression. For example, we can perform the first "Return ..." part with

   return op1 + op2;

Add similar return statements for each of the other parts.

Our remaining problem is how to select the appropriate one of these return statements. One way to elicit selective behavior is by using the Java if statement, whose general pattern is:

   if ( Condition ) Statement1 [ else Statement2 ]

Here, if and else are Java keywords, Condition is a Java boolean expression, and Statement1 and Statement2 are either individual or compound Java statements (groups of statements in { } braces). The brackets in the pattern ([ and ]) are used to indicate that the else Statement2 can be omitted. Note that nothing prevents either Statement1 or Statement2 from being another if statement.

This pattern generates three different forms of the if statement. If the else Statement2 is omitted, we get the first form:

   if ( Condition )
      Statement

which is sometimes called a simple or single-branch if, since it allows the selective execution of a single section of code.

By contrast, an if statement that has an else Statement2 portion:

   if ( Condition )
      Statement1
   else
      Statement2

is sometimes called a two-branch if, since it allows the selective execution of either of two sections of code.

A third form of the statement occurs when Statement2 is another if statement:

   if ( Condition1 )
      Statement1
   else if  ( Condition2 )
      Statement2
   ...
   else if ( ConditionN )
      StatementN
   else
      StatementN+1

This form is called a multi-branch if, since it allows the selection of any of N+1 different sections of code. When execution reaches such a statement, Condition1 is evaluated, and if it is true, Statement1 is executed while the remaining statements are skipped. However, if Condition1 is false, control proceeds to the else part where Condition2 is evaluated, and if it is true, Statement2 is executed while the remaining statements are skipped. This behavior continues until either a true condition is found, or the final condition is determined to be false, in which case StatementN+1 is executed.

It should be evident that this multi-branch if provides exactly the behavior we need to perform step 1 of our algorithm:

   1. If operation is '+':
         Return op1 + op2.
      Otherwise, if operation is '-':
         Return op1 - op2.
      Otherwise, if operation is '*':
         Return op1 * op2.
      Otherwise, if operation is '/':
         Return op1 / op2.
      End if.

Add a multi-branch if to your apply() stub to encode this step.

Testing and Debugging.  When you are done,  execute your program and test its correctness.  When your program is correct,  save Calculate.java.  Then,  since we will be altering this program,  save a copy of this first version under the name Calculate1.java,  and print a hard copy of it.  Then close Calculate1.java and reopen Calculate.java

 

Coding 2: Using the switch Statement

It should be evident from your testing that the multi-branch if statement that you just wrote is functionally correct, in that it solves the problem. However, it suffers from a drawback:

  • to perform the addition operation, one condition (operation == '+') is evaluated;
  • to perform subtraction, two conditions (operation == '+' followed by operation == '-') are evaluated;
  • to perform multiplication, three conditions (operation == '+' followed by operation == '-' followed by operation == '*') must be evaluated,
  • and so on.

In general, selecting Statementi using a multi-branch if statement requires the evaluation of i conditions. Since evaluation of each condition consumes time, statements that occur later in the multi-branch if statement take longer to execute than do statements that occur earlier.

In certain situations, this penalty can be avoided by using the Java switch statement, an alternative selective behavior statement. Its simplified general pattern is:

   switch ( ConstantExpression )
   {
      CaseList1 
         StatementList1
      CaseList2 
         StatementList2
      ...
      CaseListN 
         StatementListN
      default: 
         StatementListN+1
   }

where ConstantExpression is any Java expression that evaluates to a integer-compatible constant; each StatementListi is a sequence of valid Java statements, and each CaseListi is one or more Cases of the form:

   case Constant :

where Constant is an integer-compatible constant.

When execution reaches a switch statement, the following actions occur:

  1. The ConstantExpression is evaluated.
  2. If its value is present in CaseListi, then execution begins in StatementListi and proceeds, until a break statement, a return statement, or the end of the switch statement is encountered.
  3. If the value of ConstantExpression is not present in any CaseListi, then the (optional) default StatementListn+1 is executed, if present.

Note that a given Constant value can appear in only one CaseListi.

Note also that a break statement, whose pattern is

   break;

is usually used at the end of each StatementListi, to force execution to leave the switch statement. If a break (or return) is not provided, the Java switch statement has an interesting drop-through effect, so that upon reaching the end of StatementListi, execution proceeds to StatementListi+1. This can lead to very hard-to-find logic errors, if you forget about it, or are used to using the similar case statements of other languages.

An important question is: Under what circumstances should a switch statement be used, instead of an if statement?

The answer is that if an algorithm requires selective execution using multi-branch logic like the following:

   If (Variable  is equal to Value1) then
      StatementList1
   Else if (Variable is equal to  Value2) then
      StatementList2
   ...
   Else if (Variable is equal to ValueN) then
      StatementListN
   Else
      StatementListN+1
   End if.

then the algorithm can be encoded using a multi-branch if statement:

   if (Variable == Value1)
   {
      StatementList1
   }
   else if (Variable == Value2)
   {
      StatementList2
   }
   ...
   else if (Variable == ValueN)
   {
      StatementListN
   }
   else
   {
      StatementListN+1
   }

but it is usually more efficient to use a switch statement with the pattern:

   switch (Variable) 
   {
      case Value1:
         StatementList1
         break;
      case Value2:
         StatementList2
         break;
      ...
      case ValueN:
         StatementListN
         break;
      default:
         StatementListN+1
   }

The reason the switch statement solution is more efficient than the multi-branch if statement in such situations is that where execution of StatementListi in a multi-branch if requires the evaluation of i conditions, a switch statement can select StatementListi in approximately the time it takes to evaluate one condition. The switch statement thus eliminates the nonuniform execution time of statements controlled by a multi-branch if statement.

However, note the restrictions:

  • The switch can only be used to compare values that can be automatically cast to an integer (i.e., char, byte, short, or int values). It cannot be used to compare long, double, or String values.
  • The switch can only be used to perform the equality (or inequality) comparison. It cannot be used to perform the other relational comparisons (at least not directly).

Using the pattern above, replace the multi-branch if statement in the method apply() with a functionally equivalent (but more efficient) switch statement.

Testing and Debugging.  When you are done,  execute your program and test its correctness.  When your program is correct,  save Calculate.java.  Then,  since we will be altering this program,  save a copy of this second version under the name Calculate2.java,  and print a hard copy of it.  Then close Calculate2.java and reopen Calculate.java

 

Coding 3: Using Repetition

Thus far, we have seen that the Java if and switch statements use a condition (an expression that evaluates to true or false) to permit a program to execute statements selectively. For example, we could use the code fragment:

   if (op2 != 0)
      return (op1 / op2);
   else
   {
      theScreen.println("\nError: Divide by zero attempted !");
      System.exit(1);
   }

and use the condition op2 != 0 to avoid a divide-by-zero error: if op2 is not equal to zero, then the statement before the else is executed; otherwise, the compound statement following the else is executed.

In addition to facilitating selection, most modern programming languages also utilize conditions to permit statements to be executed repeatedly. These repetitive execution statements typically permit a set of statements to be executed over and over, so long as some condition evaluates to true.

As an example use of this capability, suppose that we have a list of lengths that we need to convert from English to metric system measurements. Without repetition, converting these lengths requires that we execute the conversion program a separate time for each value on the list. However, by adding a repetitive execution statement to that program, we can convert each value in the list with a single execution of the program.

Java provides four different loops (well, three, actually) that we will examine in this exercise. These are called the while loop, the do loop, the for loop, and what we call the forever loop.

 

Adding a New Function

Next we make an improved calculator program that has more functionality and is more user-friendly than the one we have just written.  More precisely,  the skeleton program Calculate2.java differs from our previous version by supporting a fifth calculator function:  the exponentiation operation.  Follow the usual procedure of creating a project for this exercise,  save a copy of Calculate2.java along with the ann and hoj packages there.  Add them to your project and then personalize the opening documentation of Calculate2.java

You may recall that floating-point exponentiation is available in Java via the static method Math.pow() in the Math class.  While we might wish to use it to implement a power method for fractions,&nbs; we will not use Math.pow(double base, double exponent),  but will instead use loops to write our own exponentiation instance method for the Fraction class which will also be called called pow(int exponent).

 

The Exponentiation Operation

The exponentiation operation xn should be a familiar one,  since we the static pow method is part of the Math class.

   Math.pow(x,n)

performs exponentiation on the base x and the exponent n. We will implement this operation for the Fraction class by adding the instance method Fraction pow(int exponent),  such that a call:

   fraction.pow(n)

will compute and return fraction raised to the power n.  To simplify our task,  we will restrict values of n to integers.  You will,  however,  expected to correctly handle negative and zero values of n

 

Method Design

As usual, we begin by using object-centered design to carefully design an exponentiation method. However, before we can describe its behavior, some simple analysis may shed light on what our method must do.

Method Analysis. When faced with a new problem, it is often helpful to solve it "by hand." For example, to calculate power(2,0), power(2,1), power(2,3) and power(2,5) by hand, we might write:

power(2,0)

power(2,1)

power(2,2)

power(2,3)

Return 1.

1 * 2 = 2;

1 * 2 = 2;

1 * 2 = 2;

 

Return 2;

2 * 2 = 4;

2 * 2 = 4;

 

 

Return 4.

4 * 2 = 8;

 

 

 

Return 8.

If we examine the pattern in these "by hand" computations, we might construct the following general solution:

   power(x, n)
   Initialize result to 1/1.
   Set result to result * base. -
   Set result to result * base.  |
   Set result to result * base.  | exponent
   ...                           | times 
   Set result to result * base.  |
   Set result to result * base. -
   Return result.

This provides us with the information needed to describe our method's behavior.

Method Behavior. We can describe what we want to happen as follows:

   Our method should receive a base value and an exponent value
   from the caller of the method.  It should initialize result
   to one, and then repeatedly multiple result by the base value,
   with the number of repetitions being the exponent value. 
   Our method should then return result.

Method Objects. From our behavioral description, we can identify the following objects:

Description

Type

Kind

Movement

Name

The base value

Fraction

varying

received

base

The exponent value

int

varying

received

exponent

The result value

Fraction

varying

returned

result

From this, we can specify the task of our method as follows:

   Receive: base, a Fraction;
            exponent, an int.
   Return: result, a Fraction = base^exponent.

Using this specification, go to Calculate2.java and replace the line

   // ... replace this line with the definition of power() ...

with a method stub for power(). Then uncomment the call to power() within apply() and use the compiler to test the syntax of what you have written.

Note that the call to power() uses int() to convert the Fraction argument op2 to an integer. Without this conversion, our attempt to pass a Fraction argument into an int parameter would result in a compilation error.

Method Operations. From our behavioral description, we have the following operations:

Description

Defined?

Name

Module?

1

Receive base and exponent

yes

method call
mechanism

built-in

2

Initialize result to 1/1

yes

Fraction()

Fraction

3

Set result to result*base

yes

mult

Fraction

4

Repeat (3), counting from 1 to exponent

yes

for

built-in

5

Return result

yes

return

built-in

Method Algorithm. We can organize these operations into the following algorithm:

   0. Receive base and exponent from caller.
   1. Initialize result to 1.0;
   2. For each count from 1 to exponent:
       result *= base.
   3. Return result.

 

Coding

As we have seen before, the Java for loop is designed to facilitate the counting behavior required by step 2. Recall that its simplified pattern is:

   for (Type Var = Start; Var <= Stop; Var++)
      Statement

where Type is a Java numeric type, Var is the loop control variable used to do the counting, Start is the first value, and Stop is the last value. Using this information, complete the stub of method power(). Then compile and test Calculate2.java, checking that the exponentiation works for a variety of values.

 

Characterizing Loops

The pattern for a Java for loop is actually more general than we have indicated:

   for (InitializationExpr; Condition; StepExpr)
      Statement

where InitializationExpr is any initialization expression, Condition is any boolean expression, and StepExpr is an arbitrary expression. For example, if for some reason we wanted to count downwards and output the multiples of 12 from 100 to 1, then we could write:

   for (int i = 100; i >= 1; i--)
      theScreen.println(12 * i);

Such a loop will continue to execute so long as the condition i >= 1 evaluates to true.

Java for loops are thus controlled by conditions, just as if statements are controlled by conditions. As we shall see, each of the other Java loop statements are also controlled by conditions.

A loop may be categorized by when it evaluates its condition, with respect to the statements it repeats:

  1. Pretest loops evaluate their condition before the loop's statements.
  2. Posttest loops evaluate their condition after the loop's statements.
  3. Unrestricted loops evaluate their condition before, after, or within the loop's statements.

The for loop is a pretest loop, because it evaluates its condition before the loop's statement is executed (don't take my word for it -- check back and see!) However, the for loop is designed primarily for problems that involve counting through ranges of numbers, or problems in which the number of repetitions can be determined in advance. As we shall see, there are many problems that do not fit this pattern.

The other three Java loops differ from the for loop in that they are general-purpose loops, designed for problems where the number of repetitions is not known in advance. Why are there three of them? Because

  1. The Java while loop provides a general pretest loop;
  2. The Java do loop provides a general posttest loop; and
  3. The Java forever loop provides a general unrestricted loop.

To see why this is significant, let's examine the problem of getting a menu-choice and ensuring that it is valid.

 

Getting a Valid Menu Choice

The program in Calculate2.java uses method getMenuChoice() to get the user's menu choice:

	private static char getMenuChoice(String menu)
	{
		theScreen.print(menu);
		char choice;
		choice = theKeyboard.readChar();
		return choice;
	}

Getting a menu choice is a potential source of user error, because the user could enter an invalid choice.

One way to handle such errors is to repeatedly display the menu and input the user's choice, so long as they continue to enter invalid menu choices. Since there is no way to predict in advance how many erroneous choices the user may enter, the for loop is not an appropriate statement to use in this situation.

The general-purpose loops give us a way to handle user errors, but we must decide which one to use. To do so, we can begin by writing a partial algorithm for this problem using a generic Loop statement, in which we don't worry (for the moment) how control will leave the loop:

   Loop:
      a. Display menu.
      b. Read choice.
   End loop.
   Return choice.

Every loop has a continuation condition, that defines the circumstances under which we want repetition to continue; and a termination condition, that defines the circumstances under which repetition should terminate. (A continuation condition is usually the negation of the termination condition.) To determine how control should leave the loop, we must identify these conditions. For this particular problem, we want repetition to continue so long as

   choice is an invalid menu choice

and terminate when

   choice is a valid menu choice

so these are our continuation and termination conditions, respectively.

Since choice is not known until after Step (b), following (b) is the logical place to exit the loop, which we can describe using our termination condition as follows:

   Loop:
      a. Display menu.
      b. Read choice.
      c. If choice is a valid menu choice, exit the loop.
   End loop.
   Return choice.

In this algorithm, it is apparent that the controlling condition is evaluated at the bottom of the loop, which implies that a post-test loop is the appropriate loop to choose.

In Java, the post-test loop is called the do loop, and its pattern is as follows:

   do
   {
      Statements
   }
   while ( Condition );

When execution reaches such a loop, the following actions occur:

  1. Statements execute.
  2. Condition is evaluated.
  3. If Condition is true: control returns to step 1;
    Otherwise, control proceeds to the next statement after the loop.

Note that since repetition continues so long as the condition is true, a do loop uses a continuation condition.

Since its Condition is not evaluated until after the first execution of Statements, the do loop guarantees that Statements will be executed one or more times. For this reason, the do loop is said to exhibit one-trip behavior. We will therefore implement the preceding algorithm using a do loop.

The notion of a "valid" menu choice is a bit tricky. One way to handle it is to require that the valid menu choices be consecutive letters of the alphabet (e.g., 'a' through 'e'). If we then pass the first and last valid choices to getMenuChoice() as arguments:

   char operator = getMenuChoice(MENU, 'a', 'e');

and add parameters to the method definition to store these arguments:

   private static char getMenuChoice(String menu, 
                             char firstChoice, char lastChoice)

Since firstChoice and lastChoice will define the range of valid choices, we can express our loop's continuation condition in terms of those values: choice is invalid if and only if

   choice < firstChoice || choice > lastChoice

We are then ready to add a do loop to the method to implement our algorithm:

   private static char getMenuChoice(String menu, 
                             char firstChoice, char lastChoice)
   {
     char choice;
     do
     {
       theScreen.print(menu);
       choice = theKeyboard.readChar();
     }
     while (choice < firstChoice || choice > lastChoice);
     
     return choice;
   }

Note the necessity of not declaring choice within the loop, since doing so would make it local to the loop, and inaccessible by the return statement.

Make the necessary modifications to Calculate2.java so that getMenuChoice() will only return a valid menu choice. Then thoroughly test your code with invalid and valid values. Continue when it works correctly.

 

Getting Valid Numeric Input

Another potential source of error occurs when our program reads values for op1 and op2 from the keyboard -- the user might enter some nonnumeric value. Currently the code for reading in the two values is:

	theScreen.print("\nNow enter your operands: ");
	double op1, op2;
	op1 = theKeyboard.readDouble();
	op2 = theKeyboard.readDouble();
You will need to modify this in order to receive fractional values from the keyboard. 

 

Exceptions

If you look at the code in the ann package, you will find that the readDouble() method of KeyBoard invokes the parseDouble() method of Fraction. In Java an error is indicated by an exception being thrown and the method parseDouble() throws a NumberFormatException whenever it is unsuccessful. If the user were to enter 34.3.44 for one of their values, parseDouble() will fail and throw the exception.  Now recall that reading Fractions can also generate errors which must be thrown. 

When an exception is thrown, it can be caught. This provides the implementer with the option of taking intelligent action in the case of an error. Currently our code does not catch the error. It will just display an error message and quit. To catch an exception we need to wrap the code that may fail in a try/catch block. In its simplest form the try/catch has the following form.

   try
   {
      Statements that may throw exception
   }
   catch ( ExceptionType ex )
   {
      Statements to handle exception
   };

The code inside the catch will not be executed unless an exception of the appropriate type is generated in the try block.

Pretest Loops

Lets think about out problem again. Suppose we wished to give the user another chance to enter valid values, instead of just terminating the program? Clearly we need to have a loop that will continue executing until we get two good values.

Once again we could use a do loop to solve this problem. But lets use a while loop this time for contrast. Note: Some programmers avoid the do loop for the following reasons:

  • They believe that having the continuation condition at the top of the loop makes code easier to read and understand.
  • It is easy to force a while loop to exhibit one-trip behavior if needed.

Like many other languages the Java pretest loop is called the while loop, and its pattern is as follows:

   while ( Condition )
      Statement

As usual, Condition is any Java expression that evaluates to true or false, and Statement can be either a single or compound Java statement. Statement is often referred to as the body of the loop.

When execution reaches this statement, the following actions occur:

   I. Condition is evaluated (before Statement is executed);
   II. If Condition evaluates to false, then
         Statement is bypassed, and control proceeds to the statement after the loop;
      Otherwise
         A. Statement is executed; and
         B. execution then returns to step I;
      End if.

Since Statement may be executed zero or more times, the while loop is said to exhibit zero-trip behavior (i.e., the Statement controlled by a while loop may go unexecuted, if Condition is initially false).

There are some standard coding patterns that use the while loop. One of them is called a sentinel loop.

Design

The first thing to do is to list the operations that we may need to do in the loop. One tricky point is that we may fail on reading the first data value. In that case, we will need to discard the unread characters for the second data value.

  1. Display a prompt.
  2. Read op1 and op2.
  3. Print an error message. (if needed)
  4. Discard unread characters. (if needed)

The first two tasks must be done at least once. If we use a while loop this means that we must either do them once outside the loop or force the loop to execute at least once. We will choose the second option.

We will use a boolean value called goodInput to control the loop. Lets start our algorithm without using try/catch:

   a. Set goodInput to false
   b. Loop (pretest), so long as goodInput is false:
      1) Display a prompt for two real values.
      2) Input op1 and op2.
      3) Set goodInput to true.
   End loop.

What we have here is the "normal" operation for the loop. If there is not an input error, the pretest will succeed, the data values will be read, the pretest will fail, and all will be well. Remember though, we have some operations that we must do only if there is a failure. But this is exactly the behavior that a catch exhibits. Our modified algorithm is:

   a. Set goodInput to false
   b. Loop (pretest), so long as goodInput is false:
      Try:
         1) Display a prompt for two real values.
         2) Input op1 and op2.
         3) Set goodInput to true.
      Catch:
         A) Display an error message.
         B) Discard unread characters.
   End loop.

 

In Calculate2.java, add a while loop to encode our modified algorithm.

Note that the condition

   !goodInput

can be used to control the while loop.

We can use

   junk = theScreen.readLine();

to discard any unread input.

Note also the type of error that we are trying to catch is NumberFormatException.

Translate and thoroughly test what you have written. Continue when what you have written makes the entry of numeric values "foolproof."

 

One Execution, Multiple Calculations

The algorithm that our program is using is basically as follows:

   0. Display a greeting.
   1. Display a menu of operations and read operation,
       guaranteed to be a valid menu operation.
   2. Set goodInput to false
   3. Loop (pretest), so long as goodInput is false:
      Try:
         a) Display a prompt for two real values.
         b) Input op1 and op2.
         c) Set goodInput to true.
      Catch:
         A) Display an error message.
         B) Discard unread characters.
   End loop.
   4. Compute result by applying operation to op1 and op2.
   5. Display result.

Using this algorithm, we have to rerun our program for every calculation, which is inconvenient for the user. A more convenient calculator would "wrap" some of the statements in a loop, so that the user could perform multiple calculations without having to rerun the program. To do so, we might modify our algorithm:

   i.  Display a greeting.
   ii. Loop:
      1. Display a menu of operations and read operation,
          guaranteed to be a valid menu operation.
      2. Set goodInput to false
      3. Loop (pretest), so long as goodInput is false:
         Try:
            a) Display a prompt for two real values.
            b) Input op1 and op2.
            c) Set goodInput to true.
         Catch:
            A) Display an error message.
            B) Discard unread characters.
      End loop.
      4. Compute result by applying operation to op1 and op2.
      5. Display result.
   End loop.

In order to determine which kind of loop to use, we must determine where to evaluate the loop's termination condition, which we might express as:

   the user does not want to quit

One way to have the user indicate that they want to quit is to view quitting as an operation, and provide an additional menu choice (i.e., 'f') by which the user can indicate that they want to quit. The condition

   operation == 'f'

thus evaluates to true if the user wishes to quit.

So when do we check this condition? The ideal place is to evaluate it as soon as it can be known, namely immediately following the input of operation:

Put differently, if the user wants to quit, steps 2 through 5 should not be executed -- execution should immediately leave the loop. We thus have a situation where the loop's condition should not be evaluated at the loop's beginning (eliminating the pretest loop), nor at its end (eliminating the posttest loop), but in its middle (suggesting the unrestricted loop):

   i.  Display a greeting.
   ii. Loop:
      1. Display a menu of operations and read operation,
          guaranteed to be a valid menu operation.
 
      2. If operation is quit, exit the loop.
      3. Set goodInput to false
      4. Loop (pretest), so long as goodInput is false:
         Try:
            a) Display a prompt for two real values.
            b) Input op1 and op2.
            c) Set goodInput to true.
         Catch:
            A) Display an error message.
            B) Discard unread characters.
      End loop.
      5. Compute result by applying operation to op1 and op2.
      6. Display result.
   End loop.

In Java, the unrestricted loop is a simplification of the while loop that we call the forever loop, that has the following pattern:

   while (true)
   {
      StatementList1

      if ( Condition ) break;

      StatementList2
   }

Since the while condition always evaluates to true, it is an infinite loop. Of course, we do not want an infinite number of repetitions, but instead want execution to leave the loop when a termination condition becomes true. As shown in the pattern above, this can be accomplished by placing an if statement that uses a termination condition to control a break statement (called an if-break combination) within the body of the loop.

When a break statement is executed, execution is immediately transferred out of the forever loop to the first statement following the loop. By only selecting the break statement when Condition is true, a forever loop's repetition can be controlled in a manner similar to the other general-purpose loops.

As a result, this loop can be used to solve our problem, by making StatementList1 step 1 of our modified algorithm, and making StatementList2 steps 3 through 6. Modify your source program to incorporate this approach, and then translate and test the newly added statements.

Note that this loop can be used to solve a sentinel loop problem without redundant statements. For example, here is code that will read and process data values until the user enters 0:

   while (true)                                 // LOOP:
   {
      theScreen.print("Type in a data value "); //  executed one or more times
      data = theKeyboard.readInt();             //  executed one or more times

      if ( data == 0) break;                    //  if zero, exit the loop

      processData(data);                        //  executed zero or more times
   }

Because it eliminates redundant coding, some programmers prefer the forever loop in situations where some statements must be executed zero or more times and other statements must be executed one or more times.

Implement an unrestricted loop to allow the user to do multiple computations in Calculate2.java.

Please modify the commands to correpond to the arithmetic operators shown below before you turn in your work. You will need to add the exit command x to the calculator. Calculate2 will have the following functions when complete:
  • + to perform addition
  • - to perform subtraction
  • * to perform multiplication
  • / to perform division
  • ^ to perform exponentiation
  • X to exit
In the next chapter we will learn how to pass a list of valid commands to getMenuChoice. For now, simply build in the valid alternatives rather than passing them as a list. You will only need to pass the menu to the getMenuChoice method. This means that you will need to modify getMenuChoice written above before you turn in Calculate2. Please try to write your program so that it can accept a command and the data for the command all on one line or on either two or three lines. This will be much more "robust" than insisting on a rigid input method.

Testing and Debugging

Compile and test what you have written.  When it is correct,  save your work and turn in your final version of Calculate electronically.  Remember to use Javadoc to generate documentation html pages. 

 

Phrases you should now understand:

Condition, Boolean Expression, Relational Operator, Logical Operator, Selective Execution, If Statement, Switch Statement, Condition, Repetitive Execution, For Loop, While Loop, Do Loop, Forever Loop, General-Purpose Loop, Counting Loop, Zero-Trip Behavior, One-Trip Behavior, Pretest Loop, Posttest Loop, Unrestricted Loop, Break Statement, If-Break Combination.

Prev | Next | Lab 7-8

 
  Home

Schedule

Help

Lab 0

Lab 1

Lab 2

Lab 3

Lab 4

Lab 5

Lab 6

Lab 7-8

Lab 9

Lab 10

Lab 11

Lab 12

Lab 13






Last modified: 2007 NOV 05
bnostran@syr.edu