CSc 150
Project 6 -- The Infix to Postfix Converter
Due: Wednesday, May 19, 2010
Objectives
- Use the Stack ADT
- Practice with postfix expressions
- Practice using Java interfaces and object casting
- Learn how to enforce the design goal of robustness
Your Mission
Way back when, you learned to evaluate mathematical expressions
using parentheses. For example, the parentheses in the expression
(6-2)*8
told you that the subtraction operation should be performed before the
multiplication. If the parentheses were not there, you would follow
the precedence order of operations which says
- Exponents are evaluated first from left to right
- Multiplication and division operations are performed next, from left
to right
- Addition and subtraction operations are performed last, from left
to right
So, for example, in the expression
24/2^3-1
the order of operations says that the exponent (2^3) should
be done first, followed by the division, and finally the subtraction,
giving the answer of 2.
Without this order of operations, an expression like this one would
be ambiguous without parentheses. This "normal" way of writing
mathematical expressions is called infix notation.
On the other hand, a mathematical expression written in postfix
notation doesn't need an order of operations or parentheses.
Postfix expressions are never
ambiguous. In postfix notation, the operator is written after
the operands. Here are some examples:
| infix | postfix |
| 7+1 | 7 1 + |
| (6-2)*8 | 6 2 - 8 * |
| 24/2^3-1 | 24 2 3 ^ / 1 - |
Your assignment is to write a computer program that will convert
parenthesized or unparenthesized infix expressions into their
equivalent postfix expressions. You will be reading a list of infix
expressions from a data file. You should display (to System.out)
each infix expression along with its equivalent postfix expression.
The Algorithm
Infix to postfix conversion can be accomplished with
a stack. The stack will keep track of
the operators (plus (+), minus (-), multiply (*),
divide (/), exponent (^)) and parentheses.
To convert an infix expression into
postfix, follow these rules:
- Read each token (each operand, operator, or parenthesis)
from left to right. Only one pass is required.
- If the next token is an operand (represented by a capital
letter), immediately append it to the postfix string.
- If the next token is an operator, pop and append to the postfix string
every operator on the stack until one of the following conditions
occurs:
- the stack is empty
- the top of the stack is a left parenthesis (which stays on the
stack)
- the operator on top of the stack has a lower precedence than
the current operator
Then push the current operator onto the stack.
- If the next token is a left parenthesis, push it onto the stack.
- If the next token is a right parenthesis, pop and append
to the postfix string all operators on
the stack down to the most recently scanned left parenthesis. Then
discard this pair of parentheses.
- If the next token is a semicolon, then the infix expression has
been completely scanned. However, the stack may still contain
some operators. (Why?) All remaining operators should be popped and
appended to the postfix string.
Here's how the algorithm works on the infix expression (A+B*(C-D))/E;
The rule number at the end of each line indicates which rule listed
above was used to reach the current state from that of the previous
line.
| Next token | Stack | Postfix string | Rule |
| ( | ( | | 4 |
| A | ( | A | 2 |
| + | ( + | A | 3 |
| B | ( + | AB | 2 |
| * | ( + * | AB | 3 |
| ( | ( + * ( | AB | 4 |
| C | ( + * ( | ABC | 2 |
| - | ( + * ( - | ABC | 3 |
| D | ( + * ( - | ABCD | 2 |
| ) | ( + * | ABCD- | 5 |
| ) | | ABCD-*+ | 5 |
| / | / | ABCD-*+ | 3 |
| E | / | ABCD-*+E | 2 |
| ; | | ABCD-*+E/ | 6 |
The Details
You'll be practicing with Java interfaces in this project, so while you
are free to add to the minimum requirements described below, you
shouldn't
deviate too far from the general paradigm.
To help you get started, I'm supplying several files for you to download
from BlackBoard:
- input.txt. This text file contains some infix expressions to help
you get started. The expressions are delimited by semicolons.
Of course, you should be testing your code on your own input too!
Perform the algorithm on these by hand so you understand how it
is supposed to work.
- FileReader.java. This lets you read tokens from an input
file. Check out these Javadocs to
see how it works. You should test it with the sample input
file above to be sure you understand how to use it.
- Token.java. This is a Java interface from which
you will implement classes that represent various tokens.
(The token classes are listed below).
Each implementing class will have a toString()
method to return the Token in String format and a handle()
method to dictate how to process the token when it is encountered.
The Javadocs explain further.
- Semicolon.java. This is a sample of a class that
implements the interface above. Only the method prototypes
are listed. You'll have to fill in the code yourself.
You are free to alter the code in the above files, but you are
required to use them. You are also required to
use Javadoc format for all of your documentation for this
project.
Here are the classes you'll need to write:
- A Stack class that will hold tokens to be processed. You may
use either an array-based or linked-list-based implementation. If you
use the code in the book, note that you'll have to alter it a bit since
instead of working with generic Objects, you'll want to limit the stack
to be able to handle just Tokens. You must write your own
Stack class. You may not use Java's built-in version.
- One class for each of the tokens to be processed. Each of these
classes
will be an implementation of the Token interface described
above. There are eight tokens that need to be processed:
"+", "-", "*", "/", "^", "(", ")", and ";" so you'll be making eight
new classes. I've already started one of them for you; namely,
the Semicolon class.
As mentioned above, each of these classes represents
a token whose handle() method will describe how it should
be processed according to the rules given above.
- A class named Converter that contains an
instance method, convert(), that performs the algorithm
detailed
at the top of this document. That is, it reads in a token, identifies
it,
makes a new Token type from it, and then calls the item's
handle() method to take care of it. It repeats this process
until all tokens have been read and processed. Having each item's
behavior internalized via a method like this is called
encapsulation,
another object-oriented design goal.
- And, of course, there should be a Client containing your
main() method. All main() should need to do is create
a new Converter and tell it to convert().
Feel free to add other methods to any of the classes above.
A word about precedence
When dealing with operators, you'll have develop some way of comparing
them to see which has a higher precedence. One easy way to
accomplish this is to use an int variable for each of the operators.
A higher int means a higher precedence
for that operator. You can use the following precedence values:
- Exponent: 3
- Multiplication and Division: 2
- Addition and Subtraction: 1
Then, comparing two tokens is just a matter of comparing their
int values.
Why do it this way???
As you get into this project, it may seem that it would be quicker to
code this algorithm in a straightforward manner without using the
interface
and all the implementing classes. In fact, it probably would be
quicker, but not very well-designed. By using the interface, you
control
exactly what the stack can (and cannot) hold. Instead of holding
generic
Strings or Objects, the stack can only contain Tokens. By
limiting
the stack in this way, you add robustness to the code by
preventing the
stack from being misused in ways you cannot foresee. It also allows for
the individual tokens to encapsulate their own behavior within their own
classes. That way, if you modify the program by, say, adding the modulo
operator (%), it can be done without touching the code for how division
works.
Where do I start???
There are a lot of pieces to this assignment (you'll have at least 12
classes total by the time you're done). So use top-down design to help
you manage it all. Here's a good way to approach this:
- I'm giving you a bunch of code to begin with. So studying and
testing them should be your first priority. Understand the interface.
Check out the input. Process the input on paper first so you'll
(1) know what the right answers are and (2) have
a better grasp on how the algorithm works. Test the FileReader
class to be sure it's giving you tokens correctly. Try to simply read
the input file and then print it to System.out.
- The primary algorithm at the top of this document is handled by
Converter. Get a good understanding on exactly what
this class is doing on its own and what tasks it is subcontracting out
to other methods. Converter is the one that's
repeatedly getting
tokens, figuring out which one it is, and calling the appropriate
handle() method. Think of handle() this way:
instead of you writing
all of the code in Converter (which would be very
messy), you'll simply let each token "handle" itself. So once you've
determined that a token is, say, "+", you'll turn it into a Plus object,
and tell it to handle() itself. And it will be each token's
version of handle() that does the work of pushing on the stack,
popping it, or whatever. That makes each token responsible
for doing the right thing and makes your code much more modular.
This is a key part of the algorithm so you should make sure you
understand what this means before going on (and come see me if you
don't).
- Do one Token at a time. Don't try to take care of all the
tokens all at once. Concentrate on being able to read a "+" and get it
on the stack. Now can you pop it off and get it back?
Can you correctly convert an infix expression that just uses
"+" like A+B+C?
Grading
This project is worth 50 points divided up this way:
- 20 points for correct output.
- 30 points for program design. This includes:
- good modularity. It's up to you to decide what
methods you should have in what classes.
- correct use of the interface
- coherent code. Don't make things overly-complicated.
Make sure your code reflects the logic of the assignment.
- good understandability, including Javadoc format for
all documentation. The @param and @returns
tags should be used for all parameters and return values.
As always, practice good programming skills:
- Use good comments and variables names
- Test each method individually
- Use good debugging skills: tracing and checkpointing. Use the
debugger. It is your friend.
Remember to turn in both a paper and an electronic copy of your project.
Having trouble? Don't wait until the last minute! Come see me and get
your
$80 worth.
Gentle Reminder
Programming assignments, like homework
assignments, are individual projects.
I encourage you to talk to
others about the general nature of the project and ideas about how to
pursue it.
However, the technical work, the writing, and the inspiration behind
these must
be substantially your own. If any person besides you contributes in any
way to
the project, you must credit their work on your homework. Similarly, if
you
include information that you have gleaned from other published sources,
you must
cite them as references. Looking at, and/or copying, other people's
programs or
written work is inappropriate, and will be considered cheating.
Return
to
Project Index