CSCI 1321 (Principles of Algorithm Design II), Fall 2001:
Homework 71

Assigned:
November 20, 2001.

Due:
November 29, 2001, at 5pm.

Credit:
40 points.


Contents

Reading

The textbook's chapter 6 covers defining templatized classes. Chapter 7 discusses stacks. Be sure to read Section 7.4, concentrating on reverse Polish notation expressions.

Overview

You are to implement a stack data structure capable of holding any element type. Then you are to write code using the stack to evaluate a Boolean expression written in reverse Polish notation.

Stacks

Introduction

A stack is a last-in/first-out data structure with objects arranged in linear order. That is, it permits easy access only from one end. Entries can be added or removed only at the rightmost end. For example, the STL stack class class implements a stack.

Your implementation should support the operations listed in the following table. These operations are similar but not identical to those provided by the STL stack class.

Function prototype Example use Explanation
item_type item_type x; type of items on stack.
size_type size_type n; type for size of stack (number of elements).
stack<T>(void) stack<int> s; creates a stack of elements with type T but no items.
bool empty(void) const; bool b = s.empty(); returns true if stack has no elements, false otherwise.
size_type size(void) const; stack<int>::size_type sz = s.size(); returns number of elements currently in stack.
void push(const item_type & x); s.push(x); adds x to stack.
void pop(void); s.pop(); pops (removes) top element of stack. Nothing is returned. It is the user's responsibility to ensure the stack is not empty before calling this function.
item_type top(void) const; int x = s.top(); returns top element of stack without changing the stack. It is the user's responsibility to ensure the stack is not empty before calling this function.

Stack implementation

You can choose any implementation strategy you like for your stack class except that you may not use the STL stack class. It should be possible to use your templatized class to create and manipulate stacks of ints, doubles, strings, bools, etc., with any number of elements. Your implementation should correctly use dynamic memory (i.e., deep rather than shallow copies, no memory leaks, etc.). Observe, however, that you may be able to achieve this goal with very little effort, if you implement your class using a class that already uses dynamic memory correctly (as we did when we defined a double-ended queue class deque.h using our doubly-linked-list (dll) class). You may similarly use any class we have defined in lecture, or any STL class (except for stack).

Stack implementation tips

Boolean expressions

Boolean expressions and reverse Polish notation

A Boolean expression consists of constants true and false connected together by Boolean operators &&, ||, =>, !, and ==, and possibly parentheses. For example,

(true && false) || (! true && ! false)
and
! false || true
are Boolean expressions. Using infix notation, where the Boolean operators appear between their operands, can require using parentheses. Instead, we will use reverse Polish notation. Using this notation, the previous expressions are written as
true false && true ! false ! && ||
and
false ! true ||
Reverse Polish notation first lists the two operands (using reverse Polish notation, if they are expressions) and then the operator. For example: In the second example, the first operand is ! false, the operator is ||, and the second operand is true. The reverse Polish notation for the first operand is false !. Listing the two operands and then the operator yields the expression.

Well-formed expressions

Intuitively, a well-formed expression has the correct number of operands and operators arranged in the correct order. It is defined recursively:

A well-formed expression is either true, false, or an expression $ p$ $ q$ &&, $ p$ $ q$ ||, $ p$ $ q$ =>, $ p$ $ q$ ==, or $ p$ !, where $ p$ and $ q$ are well-formed expressions.

Truth tables

To evaluate Boolean expressions, we need to be able to evaluate the simplest Boolean expressions, as follows.

We could also express these rules in the form of truth tables as follows:

&& true false
true true false
false false false

|| true false
true true true
false true false

!  
true false
false true

=> true false
true true false
false true true

== true false
true true false
false false true

Determining a Boolean expression's value

Evaluating an expression in reverse Polish notation is easy using a stack, as in the following example.

Step Stack Expression left to scan
1 $ true false && true ! false ! && || $
2 $ true false && true ! false ! && || $
3 $ true false && true ! false ! && || $
4 $ false true ! false ! && || $
5 $ false true ! false ! && || $
6 $ false false false ! && || $
7 $ false false false ! && || $
8 $ false false true && || $
9 $ false false || $
10 $ false $

Initially, the stack is empty; for expositional purposes, we use $ to denote the bottom of the stack so we can tell it is empty. Initially, we start with the entire expression; we mark its end using a $. The rules are:

For example, the first two steps move Booleans from the expression to the stack. In the third step, the && operator beginning the expression is removed, the top two Boolean expressions are popped off the stack, and the result is pushed on the stack. In step 10, the entire expression has been processed. Since there is one Boolean on the stack, it is the value of the expression and the expression was well-formed.

Programming an evaluator

In this part of the assignment, you are to add code to program evaluate-bool.cpp. Specifically, you are to write a function evaluate() evaluating a Boolean expression. As indicated by the function prototype, the function is to have two parameters. The first (input) parameter is an expression in reverse Polish notation, represented as a vector<string>. The second (output) parameter is the value of the expression. If the input expression is well-formed, the function should store its value (expressed as a string -- ``true'' or ``false'') in the second parameter and return the Boolean value true. If the input expression is not well-formed, the function should return the Boolean value false. The provided code reads a Boolean expression from standard input, calls evaluate(), and prints the result.

Input, as indicated in the header comments, is a Boolean expression in reverse Polish notation. Here are some examples of possible input expressions:

true false ! ||
false false &&
true false
false true && &&
Observe that the last two expressions are not well-formed. This should be detected by your evaluate() function. Observe also that the end of the expression is indicated by the end of the vector; that is, there is no explicit marker $ as there was in the example shown earlier.

What files do I need?

For the first part of the assignment (writing a templatized stack class), you may start from scratch, or you may make use of the following files:

For the second part of the assignment (completing the evaluate-bool program), you will need the following file:

Add to this file an evaluate() function and any needed helper functions. A prototype for evaluate() is already included. You should not need to make any changes in this program other than adding code for the evaluate() function and possibly some helper functions.

What to turn in

Submit the following two source-code files:

You do not need to submit a test program for the stack class. Submit your source code as described in the Guidelines for Programming Assignments. For this assignment use a subject line of ``cs1321 hw 7''.

Hints, tips, etc.

Program debugging

The gdb debugger allows you to run your program in stop-motion form, i.e., to step through it a line at a time, examining variables as you go. This section attempts to present just enough information about gdb to get you started; for more information, see J. Oldham's short introduction, or the complete on-line manual. To use gdb, proceed as follows.

  1. Compile your program using the -g compiler flag, e.g.,
    g++ -g -Wall -pedantic foo.cc -o foo
    This causes the compiler to write information used by the debugger.

  2. Start gdb by typing
    gdb foo
    (Replace foo with the name of your executable, e.g., a.out.)

  3. Set up to step through your program by typing the following gdb commands:
    break main
    run
    If your program needs command-line arguments, include them in the run command, e.g.,
    run anArgument anotherArgument

  4. Use the following commands to step through your program and examine variables:

    Just pressing return repeats the most recent command again.

  5. Exit gdb by typing q or quit.

gdb also runs very nicely under emacs and xemacs; the main editor window is split into two windows, one for gdb commands and output and the other showing source code (with an arrow indicating the next line to execute). To try this out, start emacs or xemacs and type M-x gdb. (The M-x is ``meta-x'', probably either Alt-x or ESC-x on your keyboard.) You will be prompted for the name of the program; type in the name of your executable (e.g., a.out or foo).

You might also want to try xxgdb, which provides a graphical interface for gdb. Start it up by typing xxgdb foo, where foo is the name of your executable.



Footnotes

... 71
© 2001 Jeffrey D. Oldham (oldham@cs.stanford.edu) and Berna L. Massingill (bmassing@cs.trinity.edu). All rights reserved. This document may not be redistributed in any form without the express permission of at least one of the authors.


Berna Massingill
2001-11-19