Course Home | Homework | Programming | Schedule & Lecture Notes | Submit

Saint Louis University

Computer Science A220/P126
Data Structures and Object-Oriented Programming

Michael Goldwasser

Spring 2005

Dept. of Math & Computer Science

Programming Assignment 07

Arithmetic Expressions

Due: Monday, 18 April 2005, 8pm

Please see the general programming webpage for details about the programming environment for this course, guidelines for programming style, and details on electronic submission of assignments.

Collaboration Policy

For this assignment, you are allowed to work with one other student if you wish (in fact, we suggest that you do so). If any student wishes to have a partner but has not been able to locate one, please let the instructor know so that we can match up partners. You will note that there are two distinct implementation approaches required for this assignment. It may be that the partnership divides the work by having one person do one implementation, and one person another (with consultation as needed). Alternatively both pieces could be developed side-by-side.

Please make sure you adhere to the policies on academic integrity in this regard.


Contents:


Overview

The goal of this program will be to turn a parenthesized expression such as:

((((3+1)*3)/((9-5)+2))-((3*(7-4))+6))
into a valid binary tree, which can then be used to evaluate the underlying expression.

In terms of programming techniques, this assignment will explore the use of inheritence, defining a new class ExpressionTree which is a specialization of the more general BinaryTree class matching that of the text. You will not need to write any of the low-level code for representing the tree, but you will need to understand how to make use of the interface to the BinaryTree class in properly extending it for this purpose.


Expression Format

Parsers for languages such as C++ must certainly be able to perform such a task. Arithmetic expression appear in source code and must be evaluated in turn. Example 6.5 on pages 258--259 of the text discusses the use of such arithmetic expression trees.

In general, parsers deal with a variety of complications, as they allow unary operators (such as "-3"), and operators which take more than two (such as "4+8+2+12"). Furthermore, they enforce a prescribed precedence for operators in the case where the user does not properly parenthesize an expression (imagine "5 - 3 * 4 + 10").

We will consider a simple version of the problem by using only binary operators and by assuming strict parenthesization in expressions. In particular we will make the following recursive definition for a well-formed expression:

This simple recursive definition allows us to represent arbitrarily complex arithmetic expression (and understanding this recursion will allow you to write a relative simple solution to this assignment!)

You may assume that your routine will be given WELL FORMED expressions. You do not need to be concerned with how to gracefully handle improperly formed expressions.

Examples

Examples of well-formed expressions include (one per line):

13
(5 - 8)
(3 * (4/2))
((((3+1)*3)/((9-5)+2))-((3*(7-4))+6))


Driver

To save you some effort, we provide a simple text-based driver, ExpressionDriver for this assignment. If entering input from the keyboard, the user is expected to enter a well-formed expression on a single line and press return. (the driver will not explicitly check the well-formed condition; but you may assume the user enters well-formed expressions).

The driver does the low-level input, breaking the line of input into an array of what we term tokens An individual token is either a parenthesis, an operator symbol, or a numeric value. Please see the Token.h file for a self-explanatory overview of that class. After breaking the expression into an array of tokens, the driver then asks you to construct an expression tree which corresponds to this input. After doing so, it calls additional routines to echo the original expression and to evaluate the expression. The user may then continue by typing another expression, again on a single line. To exit the program, enter "Q" and hit return.

As usual, if you prefer to type your input into a file for testing, you may give a filename to the driver as a single runtime argument.


A BinaryTree class based on the text

Rather than having you create a class from scratch for representing an expression, we will have you make use of an existing BinaryTree class, modeled after the interface described in our text. Specifically, we have implemented such a class which includes all of the general Tree methods described in Chapter 6.1.2 as well as the specialized BinaryTree methods described in Chapter 6.3.1. In general, most of these methods combine to provide only an inspectable binary tree.

Please note that the constructor generates a new tree with a single external node which initially serves as the root. The element stored at the note will be a default object.

Since you will need the ability to modify your underlying tree, we have implemented the following update methods, some of which are discussed on page 294 of the text (though some are not):


Extending a BinaryTree class

Rather than having you design a brand new class, presumably with a BinaryTree as a private data member, we have decided to use this assignment to explore a natural use of inheritence. You will be implementing a ExpressionTree class which is really a specialization and extension of the BinaryTree class. Please review Ch. 2.2.1 for a detailed discussion of inheritance in C++.

The ExpressionTree class is a specialization of the BinaryTree class because the original BinaryTree is templated to hold any type of Object as an element, whereas an expression tree is defined specifically to hold elements of type Token.

The ExpressionTree class extends the BinaryTree base class by supporting the following additional functionality. Your primary task is to implement each of these new functions.


Files you will need

The files you need for this assignment can be downloaded here.

These include the following, though you will not need to modify, or even read, many of them. We will briefly discuss the purpose of each file:


Files to Submit

You should submit:


Recursion on BinaryTree's

One of the beauties of binary trees are that they lead very nicely to the use of recursion. A subtree of a binary tree appears very much to be its own tree. In terms of implemenation however, there is one catch. If you call a method such as T.leftChild(v) for a given tree T and position v, it does not actually return to you an object of class BinaryTree; it returns to you a Position within the original tree T. If you look carefully at the many code examples in Chapter 6 of the text, they base their recursion on the concept of a subtree.

The representation of a subtree is generally modeled based upon two references, one to the full tree and one to a position of that tree which is treated as the root of a subtree. In many examples of the text, such as code Fragment 6.17, both of those parameters are given explicitly. In an object-oriented framework, a member function of a tree class receives the reference to the tree itself as an implicit paramemter (i.e., this).

Of course the use of recursion is an implementation detail which is encapsulated from outside users. For example, in this assignment, we have asked for a routine evaluate(). Notice that this routine does not accept a parameter v to represent the position of the root of a subtree, even though such a parameter is needed for the recursive implementation. The solution is to have the public method serve as a wrapper for a private recursive procedure. Since the public method involves the full tree, you can simply translate this to start the recursive function with the root of the original tree as the parameter defining a subtree's root.


Efficiency

What we care most about in this program is that you get it working correctly. Some may be interested in thinking a bit about the efficiency of their routines. The straightforward implementations of the output operator and the evaluation are O(n) worst-case.

A straightforward recursive implementation of the constructor may lead to a worst case running time of n2, where n is the number of tokens in the expression. Though not required, we will point out that it is possible to implement the constructor in O(n) worst-case time. This can be done using a non-recursive implementation, and it can even be done using a recursive implementation, though it requires a good deal of thought into how to precisely define the recursion. Even the extra credit can be done in O(n) time with care.


Extra Credit (up to 2 points)

Generalized expressions

For the original assignment, we guaranteed that expressions were well-formed, and fully parenthesized. For extra credit, we ask that you write an alternate form of the constructor which accomplishes the following

  1. throws a InvalidExpressionException when the user provides an input which is not well-formed. (your original program may have had unpredictable behavior in this case)
  2. handles a more general form of expressions which are not fully parenthesized, which respect standard precedence of operators, and which recognizes the use of a unary negation operator.

Note that our files declare a separate constructor for the extra credit, so that we can still grade your original constructor without fear that your attempt at extra credit caused your original working program to misbehave.

We define a more general grammer for expression as follows:

In parsing general expression, you must follow C++'s rules for precedence of operators, namely that:

If you compare this definition to the original, you will notice that it includes all original well-formed expression and many more. Examples of such general expressions include:

1+2-3+4      = (((1+2)-3)+4)   =   4
(5)+7        = (5+7)           =  12
5-3*4+10     = ((5-(3*4))+10)  =   3
5-3*(4+10)   = (5-(3*(4+10)))  = -37
-(4+3)       = (0-(4+3))       =  -7
7*-4+3       = ((7*(0-4))+3)   = -25

To test the extra credit version of the constructor, execute the driver with two command-line arguments, the first of which is an inputfile name and the second of which is "e" (or any argument in reality). That is, ExpressionDriver inputfile e would test the extra credit constructor rather than the standard version.

We will award up to 2 extra points for this task, though we are not in any way implying that this is a minor feat to accomplish. Please make sure that the readme file announces your success.


Michael Goldwasser
CS A220/P126, Spring 2005
Last modified: Thursday, 14 April 2005
Course Home | Homework | Programming | Schedule & Lecture Notes | Submit