A5: Reverse Polish Notation
For this project, you will implement an interpreter, like the Python interpreter. (A tiny interpreter, of course!) An interpreter takes a human-readable string, parses it (breaks it down into smaller parts, called tokens), and then computes something based on those tokens.
One thing an interpreter might need is a way to evaluate mathematical expressions. In this section, we’ll see how to use stacks to do this.
Take the simple expression “3 + 4”, stored as a string. We could perhaps write a program that looked at characters from the string from left to right. First, we see the number 3. Then we see the operator +, and we realize that the operator + indicates to add the number before it, and the number after it. Finally, we get the 4, and do the addition.
The order of operations makes things trickier. Consider the string “3 + 4 * 2”. We get a 3. Then a +. Then a 4. Do we add 3 and 4? No, not yet. Then we get a * operator. Then a 2. Now multiply 4 and 2, and finally add 3 and 8. You can imagine that parentheses might make things even worse. The difficulty with computing expressions like “3 + 4 * 2” is that while we might like to process the string from left to right, the order in which computations are performed could be complicated.
Reverse Polish Notation is a way of writing mathematical expressions that easily described the order in which to perform computations, without the need for any order of operations or parentheses. Here is how you might write the above expression: “3 4 2 * +”.
Let’s compute the value of “3 4 2 * +”, reading characters from the string left to right. 3 is a number. Store it in your head. 4 is a number; store it. Then store 2. Then we see an operator, *. The * operator needs two numbers to operate on, so take the two most recent numbers that you stored, 2 and 4, multiply them, and replace them in your head with the result, 8. Then we see a the operator +. Take the two most recent numbers in your head, 8 and 3, add them, and replace those numbers with the result, 11.
An algorithm to compute Reverse Polish expressions can use a stack to provide the “last in, first out” mechanic that allows us to access the most recent numbers when we see an operator. Below is the approach for evaluating an expression in RPN. In the psuedocode, a token is a number or an operator in the string; tokens are separated by spaces.
Create an empty stackFor each token (either a number or a operator) in our RPN expression: If the token is a number Push the number onto the stack If the token is a operator Pop two numbers off of the stack Operate and push the result onto the stackIn a correctly written RPN expression, there will be one item left in the stack — pop it off and that is the answer
Here is an interactive demonstration of the algorithm at work. Step through the tokens one at a time, or press play to watch the whole expression evaluate. Numbers get pushed onto the stack; operators pop the top two values, combine them, and push the result back.
Press "Step" or "Play" to begin.
What to do
Section titled “What to do”Write an interpreter for reverse polish notation. Your interpreter should consist of a function, compute_rpn(), which should take as input a string consisting of integers and operators separated by spaces, and output the computed value of the reverse polish notation. You’ll probably want to write other helper functions as well. You may use integer division. The .split() method of the string class may be helpful to you.
Also include some test code that calls the compute_rpn() function for several legal expressions (strings). Here are two starting tests for you. (You should add at least five more, including some simple and some more complex.
print(compute_rpn("5 4 -")) # should print 1print(compute_rpn("5 13 1 - 4 / + 4 *")) # should print 32Notice one tricky point here. "5 4 -" means , not . So make sure you get the order correct as you pop values from the stack and apply operators to them.
Operator dispatch
Section titled “Operator dispatch”If you implemented the four operators with a chain of if/elif statements, notice that adding a fifth operator means adding yet another branch — and a sixth, another branch after that. Rewrite compute_rpn()’s operator handling to use a dictionary instead: a dictionary that maps each operator string (like "+") to a function that performs that operation. Look up the right function for each operator token and call it, rather than branching on the token with if/elif.
Python’s operator module provides ready-made functions for this — operator.add, operator.sub, operator.mul, and so on — so you can build the dictionary as {"+": operator.add, "-": operator.sub, ...} instead of writing your own one-line functions, though either approach is fine.
Extension (not required)
Section titled “Extension (not required)”Once operators are entries in a dictionary, adding a new one doesn’t require touching your control flow at all. Try adding an operator or two beyond the original four — for example, ^ for exponentiation, or a one-operand operator like sqrt (which would need different handling, since it only pops one value instead of two).
Feedback
Section titled “Feedback”At the top of your main Python file, add a comment that answers these questions:
- About how long did the assignment take you?
- What was hard?
- What was easy?
- Was anything in the assignment unclear?
We use your answers to improve the assignment.
Grading and requirements
Section titled “Grading and requirements”compute_rpncorrectly evaluates legal expressions using+,-,*, and/, with a stack: each number is pushed, and each operator pops two values and pushes the result.- Operands are popped in the correct order, so that
"5 4 -"gives 1, not -1. - Operators are carried out by looking up a function in a dictionary that maps each operator string to a function, not by a chain of
if/elifstatements. - Test code calls
compute_rpnon at least seven expressions, including the two given above, some simple and some more complex, with the expected value of each checked by hand.