[ Exercises | Chapter Index | Main Index ]

Solution for Programmming Exercise 8.4


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 8.4:

The source code file file Expr.java defines a class, Expr, that can be used to represent mathematical expressions involving the variable x. The expression can use the operators +, -, *, /, and ^ (where ^ represents the operation of raising a number to a power). It can use mathematical functions such as sin, cos, abs, and ln. See the source code file for full details. The Expr class uses some advanced techniques which have not yet been covered in this textbook. However, the interface is easy to understand. It contains only a constructor and two public methods.

The constructor new Expr(def) creates an Expr object defined by a given expression. The parameter, def, is a string that contains the definition. For example, new Expr("x^2") or new Expr("sin(x)+3*x"). If the parameter in the constructor call does not represent a legal expression, then the constructor throws an IllegalArgumentException. The message in the exception describes the error.

If func is a variable of type Expr and num is of type double, then func.value(num) is a function that returns the value of the expression when the number num is substituted for the variable x in the expression. For example, if Expr represents the expression 3*x+1, then func.value(5) is 3*5+1, or 16. If the expression is undefined for the specified value of x, then the special value Double.NaN is returned.

Finally, func.toString() returns the definition of the expression. This is just the string that was used in the constructor that created the expression object.

For this exercise, you should write a program that lets the user enter an expression. If the expression contains an error, print an error message. Otherwise, let the user enter some numerical values for the variable x. Print the value of the expression for each number that the user enters. However, if the expression is undefined for the specified value of x, print a message to that effect. You can use the boolean-valued function Double.isNaN(val) to check whether a number, val, is Double.NaN.

The user should be able to enter as many values of x as desired. After that, the user should be able to enter a new expression. Here is an applet that simulates my solution to this exercise, so that you can see how it works:


Discussion

A pseudocode algorithm for the program is given by:

while (true):
    Get a line of input from the user
    if the line is empty:
       break
    Convert the input line to an Expr
    Read and process the user's numbers

Converting the input line into an object of type Expr involves calling the constructor from the Expr class. This call might generate an IllegalArgumentException. The algorithm must be expanded to handle this exception and print an error message if it occurs. When an error occurs, I use a continue statement to jump back to the start of the loop without reading any numbers from the user:

while (true):
    Get a line of input from the user
    if the line is empty:
       break
    try {
       Let expression = new Expr(line)
    }
    catch (IllegalArgumentException e) {
       Print an error message
       continue   // jumps back to start of loop
    }
    Read and process the user's numbers

The last step, reading and processing the user's numbers, expands into a loop, which is nested inside the main while loop. In this loop, I could use TextIO.getDouble() to read one of the user's numbers, but instead I choose to read the user's input into a string and convert that string into a value of type double. This has two advantages: I can end the loop when the user presses return. And I can do nicer error handling than the default error-handling that is provided by TextIO. The conversion from a string, line, to a double is done using a method Double.parseDouble(string). The conversion will generate a NumberFormatException if the user's input is not a legal number. The algorithm for reading and processing the user's numbers becomes:

while (true):
    Get a line of input from the user
    if the line is empty:
       break
    try {
       Let x = Double.parseDouble(line)
    }
    catch (NumberFormatException e) {
       Print an error message
       continue
    }
    Let val = expression.value(x)
    if val is Double.NaN:
       Print an error message
    else:
       Output val

All this can be easily translated into the complete solution, which follows.


The Solution

public class FunctionEvaluator {

   public static void main(String[] args) {
   
      String line;      // A line of input read from the user.
      Expr expression;  // The definition of the function f(x).
      double x;         // A value of x for which f(x) is to be calculated.
      double val;       // The value of f(x) for the specified value of x.
      
      TextIO.putln("This program will evaluate a specified function, f(x), at");
      TextIO.putln("specified values of the variable x.  The definition of f(x)");
      TextIO.putln("can use the operators +, -, *, /, and ^ as well as mathematical");
      TextIO.putln("functions such as sin, abs, and ln.");
      
      while (true) {
      
         /* Get the function from the user.  A line of input is read and
            used to construct an object of type Expr.  If the input line is
            empty, then the loop will end, and the program will terminate. */
 
         TextIO.putln("\n\n\nEnter definition of f(x), or press return to quit.");
         TextIO.put("\nf(x) = ");
         line = TextIO.getln().trim();
         if (line.length() == 0)
            break;
            
         try {
            expression = new Expr(line);
         }
         catch (IllegalArgumentException e) {
                // An error was found in the input.  Print an error
                //    message and go back to the beginning of the loop.
            TextIO.putln("Error!  The definition of f(x) is not valid.");
            TextIO.putln(e.getMessage());
            continue;
         }
         
         /* Read values of x from the user, until the user presses return.
            If the user's input is not a legal number, print an error message.
            Otherwise, compute f(x) and print the result. */
         
         TextIO.putln("\nEnter values of x where f(x) is to be evaluated.");
         TextIO.putln("Press return to end.");
         
         while (true) {
            TextIO.put("\nx = ");
            line = TextIO.getln().trim();
            if (line.length() == 0)
               break;
            try {
               x = Double.parseDouble(line);
            }
            catch (NumberFormatException e) {
               TextIO.putln("\"" + line + "\" is not a legal number.");
               continue;
            }
            val = expression.value(x);
            if (Double.isNaN(val))
               TextIO.putln("f(" + x + ") is undefined.");
            else
               TextIO.putln("f(" + x + ") = " + val);
         }  // end while
         
      } // end while
      
      TextIO.putln("\n\n\nOK.  Bye for now.");
   
   }  // end main();
   
} // end class FunctionEvaluator

[ Exercises | Chapter Index | Main Index ]