Evaluating a Postfix Expression
Chapter Thirty-Eight
Syllabus topic Computer Science Practical 3, Module 2, "Convert expressions from prefix to postfix and evaluate them"
Pages 114 to 117 of 411
In one line
To evaluate postfix, push every operand and on every operator pop two, apply, and push the result; the answer is the single value left at the end.
The algorithm
for each token:
operand : push its value
operator: pop the right operand, pop the left operand,
apply, push the result
at the end: exactly one value should remain; it is the answer
Shorter than the conversion, with no precedence and no brackets, because the postfix form has already resolved all of that. That is what the conversion bought.
The order trap
The two pops come off in the reverse of the order the operands appeared.
5 3 -
The first pop gives 3, the second gives 5, and the answer is 5 - 3 = 2, not 3 - 5 = -2.
For + and x it makes no difference and the bug hides. For -, /, % and ^ it does, so the rule is: the first value popped is the right operand.
Built, with the trace printed
def evaluate_postfix(tokens, trace=False):
"""Evaluate a postfix expression. Returns the value."""
stack = []
if trace:
print("%-6s %-28s %s" % ("token", "action", "stack"))
for token in tokens:
if token in ("+", "-", "x", "/", "%", "^"):
if len(stack) < 2:
raise ValueError("operator '%s' needs two operands" % token)
right = stack.pop() # FIRST pop is the RIGHT operand
left = stack.pop()
if token == "+":
value = left + right
elif token == "-":
value = left - right
elif token == "x":
value = left * right
elif token == "/":
if right == 0:
raise ZeroDivisionError("division by zero in the expression")
value = left / right
elif token == "%":
value = left % right
else:
value = left ** right
stack.append(value)
action = "%s %s %s = %s" % (left, token, right, value)
else:
value = int(token)
stack.append(value)
action = "push %s" % value
if trace:
print("%-6s %-28s %s" % (token, action, stack))
if len(stack) != 1:
raise ValueError("the expression left %d values, not 1" % len(stack))
return stack[0]
print("5 3 - 2 x")
print("answer:", evaluate_postfix("5 3 - 2 x".split(), trace=True))
print()
cases = [
("2 3 +", "2 + 3"),
("5 3 -", "5 - 3"),
("2 3 4 x +", "2 + 3 x 4"),
("2 3 + 4 x", "(2 + 3) x 4"),
("2 3 2 ^ ^", "2 ^ (3 ^ 2)"),
("2 3 ^ 2 ^", "(2 ^ 3) ^ 2"),
("10 2 / 3 -", "10 / 2 - 3"),
]
for postfix, meaning in cases:
print("%-14s = %-16s -> %s" % (postfix, meaning, evaluate_postfix(postfix.split())))
print()
for bad, why in [("2 +", "not enough operands"), ("2 3", "two values left"),
("4 0 /", "division by zero")]:
try:
evaluate_postfix(bad.split())
except (ValueError, ZeroDivisionError) as e:
print("%-8s refused (%s): %s" % (bad, why, e))Evaluating a Postfix Expression
5 3 - 2 x
token action stack
5 push 5 [5]
3 push 3 [5, 3]
- 5 - 3 = 2 [2]
2 push 2 [2, 2]
x 2 x 2 = 4 [4]
answer: 4
2 3 + = 2 + 3 -> 5
5 3 - = 5 - 3 -> 2
2 3 4 x + = 2 + 3 x 4 -> 14
2 3 + 4 x = (2 + 3) x 4 -> 20
2 3 2 ^ ^ = 2 ^ (3 ^ 2) -> 512
2 3 ^ 2 ^ = (2 ^ 3) ^ 2 -> 64
10 2 / 3 - = 10 / 2 - 3 -> 2.0
2 + refused (not enough operands): operator '+' needs two operands
2 3 refused (two values left): the expression left 2 values, not 1
4 0 / refused (division by zero): division by zero in the expressionTwo rows settle chapter 36's warning with arithmetic: 2 3 2 ^ ^ is 512 and 2 3 ^ 2 ^ is 64. Those are the postfix forms of 2 ^ (3 ^ 2) and (2 ^ 3) ^ 2, and the difference is entirely in the order of the symbols.
Rows three and four are the same check for precedence: 2 3 4 x + is 14 and 2 3 + 4 x is 20.
Conversion and evaluation together
The two chapters compose: convert an infix expression, then evaluate the result, and compare against the value the expression should have.
PRECEDENCE = {"+": 1, "-": 1, "x": 2, "/": 2, "^": 3}
RIGHT = {"^"}
def to_postfix(tokens):
output, stack = [], []
for token in tokens:
if token not in PRECEDENCE and token not in "()":
output.append(token)
elif token == "(":
stack.append(token)
elif token == ")":
while stack and stack[-1] != "(":
output.append(stack.pop())
stack.pop()
else:
while (stack and stack[-1] != "("
and (PRECEDENCE[stack[-1]] > PRECEDENCE[token]
or (PRECEDENCE[stack[-1]] == PRECEDENCE[token]
and token not in RIGHT))):
output.append(stack.pop())
stack.append(token)
while stack:
output.append(stack.pop())
return output
def evaluate(tokens):
stack = []
for token in tokens:
if token in PRECEDENCE:
right, left = stack.pop(), stack.pop()
stack.append({"+": left + right, "-": left - right,
"x": left * right, "/": left / right,
"^": left ** right}[token])
else:
stack.append(int(token))
return stack[0]
checks = [
("2 + 3 x 4", 2 + 3 * 4),
("( 2 + 3 ) x 4", (2 + 3) * 4),
("10 / 2 - 3", 10 / 2 - 3),
("2 ^ 3 ^ 2", 2 ** 3 ** 2),
("7 - 2 - 1", 7 - 2 - 1),
("2 x ( 3 + 4 ) / 7", 2 * (3 + 4) / 7),
]
print("%-22s %-20s %-8s %-8s %s" % ("infix", "postfix", "ours", "Python", "agree"))
for infix, expected in checks:
postfix = to_postfix(infix.split())
ours = evaluate(postfix)
print("%-22s %-20s %-8s %-8s %s"
% (infix, " ".join(postfix), ours, expected, ours == expected))Evaluating a Postfix Expression
infix postfix ours Python agree
2 + 3 x 4 2 3 4 x + 14 14 True
( 2 + 3 ) x 4 2 3 + 4 x 20 20 True
10 / 2 - 3 10 2 / 3 - 2.0 2.0 True
2 ^ 3 ^ 2 2 3 2 ^ ^ 512 512 True
7 - 2 - 1 7 2 - 1 - 4 4 True
2 x ( 3 + 4 ) / 7 2 3 4 + x 7 / 2.0 2.0 TrueEvery row is checked against Python's own evaluation of the same expression, which knows nothing about our stack. Six independent agreements, including both associativity cases.
The cost
One pass, one push or one pop pair per token: O(n) time. The stack holds at most the operands not yet consumed, so O(n) memory, worst case for an expression like 1 2 3 4 5 + + + +.
Quick revision
- Push operands; on an operator pop two, apply, push the result; one value should remain at the end.
- The first value popped is the RIGHT operand. This matters for
-,/,%and^and hides for+
and x.
- No precedence and no brackets are needed: the conversion already resolved them.
2 3 2 ^ ^is 512 and2 3 ^ 2 ^is 64.- Errors: too few operands for an operator; more than one value left at the end; division by zero.
- O(n) time and O(n) memory.
Test yourself
1. Give the evaluation algorithm in two lines. Push each operand. On each operator, pop two values, apply the operator and push the result. At the end exactly one value remains and it is the answer.
2. Which popped value is the right operand, and which operators does it matter for? The first one popped. It matters for -, /, % and ^; for + and x the mistake is invisible.
3. Evaluate 5 3 - 2 x step by step. Push 5, push 3; the - pops 3 then 5 and pushes 5 - 3 = 2; push 2; the x pops 2 and 2 and pushes 4. The answer is 4.
4. Evaluate 2 3 2 ^ ^ and 2 3 ^ 2 ^, and say which infix expressions they are. 512 and 64. They are 2 ^ (3 ^ 2) and (2 ^ 3) ^ 2.
Evaluating a Postfix Expression
5. Name three errors the evaluator must detect. An operator with fewer than two operands available; more than one value remaining at the end; and division by zero.
6. Why does evaluation need no precedence rules? Because the postfix form already encodes the grouping in the order of its symbols, which is exactly what the conversion did.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.