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.
The gdb debugger permits running programs in ``slow motion,'' permitting examination of how a program runs. For information how to use gdb, see Jeffrey Oldham's short introduction or the notes at Stanford.
Here is a very quick introduction to gdb.
g++ -g -Wall -pedantic foo.cc -o foo
This causes the compiler to write information used by the debugger.
Note that you can also run gdb from the Unix command prompt; the command is gdb followed by the name of your executable. If you do this, you won't get an emacs window showing program source; to view source, you can use the gdb list command (type ``help list'' after starting gdb for more information).
You and possibly a CS 1321 classmate 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. Combining this with distributed source code tautology-checker.cc yields a program that checks Boolean expressions for tautologies.
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 implements a stack.
Your implementation should support all the operations listed in Table 1. These operations are similar but not identical to those provided by the STL stack class.
You can choose any implementation strategy you like for your stack class except that you may not use the STL stack class or any implementation similar to the source code in the textbook. 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 (but why implement it using dynamic memory yourself?).
typedef string value_type;
Then templatize the class and test using a different type. Using a different type will check that all occurrences of value_type were actually found.
The typename keyword is also useful whenever the compiler cannot determine that an expression is a type. For example, it is sometimes necessary to write
typedef typename stack<T>::size_type foo;
for reasons only apparent 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 variables, true, and false connected together by Boolean operators &&, ||, =>, !, and == and possibly parentheses. For example,
(x && y) || (!x && !y)
and
!p || 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
x y && x ! y ! && ||
and
p ! true ||
Reverse Polish notation lists the two operands (using reverse Polish notation) first and then the operator. For example: In the second example, the first operand is !p, the operator is ||, and the second operand is true. The reverse Polish notation for the first operand is p !. 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, a variable, or an expression p q &&, p q ||, p q =>, p q ==, or p !, where p and q are well-formed expressions.
To evaluate Boolean expressions, we need to be able to evaluate the simplest Boolean expressions:
Evaluating a reverse Polish notation is easy using a stack. See Figure 2.
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:
In addition to Boolean expressions involving only true, false, and the operators described earlier, we can write Boolean expressions involving variables. Such an expression has a value for any assignment of Boolean values to its variables. For example, consider the expression p q ||. There are 22 ways of assigning values to its two variables, since each variable can be either true or false. For each way of assigning values to p and q, we can then evaluate the resulting expression. The result is false if both p and q are false and true for the other three choices.
A tautology is a Boolean expression that evaluates to true for all possible ways of assigning values to its variables. For example,
true
is a tautology, as are
x x ==
and
x ! x ||
and
x x == y y == &&
since all evaluate to true for any way of assigning values to their variables. However,
x
and
x y =>
are not tautologies, because there is some way of assigning values to their variables that makes them evaluate to false.
Given a Boolean expression with N variables, one way of determining whether it is a tautology is to evaluate the 2N possible expressions resulting from assigning different combinations of Boolean values to its N variables. If all of them evaluate to true, the original expression is a tautology; otherwise it is not.
Write a function evaluate evaluating a Boolean expression without any variables. Given an expression in reverse Polish notation represented as vector of strings, it should return a pair of Booleans, the first indicating whether the expression was well-formed and, if well-formed, the second indicating the expression's value.
The provided code tautology-checker.cc reads a Boolean expression with variables from the standard input and cycles through all possible variable assignments, invoking evaluate() to determine the expression's value. If the expression is true for all assignments, the program indicates that it is a tautology. Otherwise, the program indicates that it is not a tautology or is not well-formed.
The user-provided expression must be in reverse Polish notation with all variables, operators, and keywords separated by whitespace. Any whitespace-delimited sequence of characters other than an operator, true, or false is considered to be a variable.
For the templatized stack class you are to write, we have provided a minimal stack.h and test program test-stack.cc.
For the tautology checker program, start with our tautology checker program and add an evaluate() function and any helper functions. A prototype for evaluate() is already included.
As for previous homeworks, working with other people during your planning phase is encouraged. For this homework, you are permitted to write the code, i.e., program, with one other person in CS 1321. To learn the material, both of you should be actively involved in the programming. Failure to do so will almost certainly hurt your comprehension of the material in the rest of the course.
Each one- or two-person team of programmers should submit only its completed implementation, consisting of the files stack.h and tautology-checker.cc. You do not need to send any other files. Please send only text documents, do not send Microsoft Word documents, PDF documents, HTML documents, etc. Please include both your names and email addresses at the top of your program.
We will test your stack implementation using our own programs and our own choice of stack elements; we will also test your completed tautology-checker program. Please be sure your code compiles without warning when using g++ -Wall -pedantic. You should not need to change anything in the tautology checker program other than adding code for the evaluate() function and possibly some helper functions, but if you are unable to resist tinkering with our code, please do not change its interface, i.e., what input it accepts and what output it produces.
See the guidelines for programming assignments for instructions on how to e-mail the programs. For this assignment use a subject line of ``cs1321 homework 7''.
If a team of two are in different sections, submit exactly once to one of the two permissible email addresses.
Given a Boolean expression with v variables and n operators, our tautology checker requires O(2v n) time. While exponential running times are acceptable for small values of v, they quickly become infeasible for larger values. If you want to see this in action, you can use Perl program generate-tautology.pl to generate input for the tautology checker. It takes one command-line argument specifying the number of variables. (To use this program, save it into a file and make the file executable with the command chmod +x generate-tautology.pl.) To time the tautology checker program, you can use timer.h.
Can we find a faster algorithm? No one has yet been successful. There is a family of NP-complete problems all of which are currently thought to be difficult to solve. We can prove that if any of these problems can be solved in polynomial time, i.e., O(nk), for some fixed k, then all these problems can be solved in polynomial time; conversely, if one of these problems can be proved to require more than polynomial time, then they all do. Satisfiability, i.e., ``Is there an assignment making the Boolean expression true?,'' is the most famous NP-complete problem. The tautology problem is at least as hard as, or harder than, satisfiability. so do not be frustrated by not finding a faster algorithm. For more information, read Foundations of Computer Science, by Alfred V. Aho and Jeffrey D. Ullman, ISBN 0-7176-8233-2, p. 649.