Project 6 -- The Infix to Postfix Converter
Due: Thursday, February 24, 2011
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 (make sure you
understand these before you try to implement the project):
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 parentheses and operators (plus (+),
minus (-), multiply (*), divide (/), exponent (^)). 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:
- 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. Read the javadocs in the file 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. See the javadocs in the file for more explanation.
- 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 implement 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 a
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 to 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 want to 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
the existing code 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.
Administrative statement
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.