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.
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.
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. |
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).
typedef string item_type;Then templatize the class and test using a different type. Using a different type will check that all occurrences of item_type were actually found.
The typename keyword is also useful whenever the compiler cannot determine that an expression is a type, as in the following example:
typedef typename stack<T>::size_type foo;Exactly when and why this is necessary is apparently only obvious to people who have compilers inside their heads. Heuristic: If the compiler becomes terribly confused about a type, and the type contains a template parameter, try adding the keyword typename before the expression.
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 || trueare 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.
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 &&, ||, =>, ==, or !, where and are well-formed expressions.
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 |
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.
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 ! ||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.
false false &&
true false
false true && &&
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.
Submit the following two source-code files:
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.
g++ -g -Wall -pedantic foo.cc -o fooThis causes the compiler to write information used by the debugger.
gdb foo(Replace foo with the name of your executable, e.g., a.out.)
break mainIf your program needs command-line arguments, include them in the run command, e.g.,
run
run anArgument anotherArgument
x = foo(10);n will take you to the line outputting the value of x, while s will take you to the first line of function foo.
cout << x << endl;
int x;then the following commands should all work:
double y[10];
pair<char,char> z;
p x
p y[5]
p z.first
Just pressing return repeats the most recent command again.
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.