3

I want to write a fucnction to evaluate a postfix expression passed as a list. So far I have got:

def evalPostfix(text):
    s = Stack()
    for symbol in text:
        if symbol in "0123456789":
            s.push(int(symbol))
        if not s.is_empty():
            if symbol == "+":
                plus = s.pop() + s.pop()
            if symbol == "-":
                plus = s.pop() - s.pop()
            if symbol == "*":
                plus = s.pop() * s.pop()
            if symbol == "/":
                plus = s.pop() / s.pop()

But I think I have the wrong approach. Help?

abcd
  • 10,215
  • 15
  • 51
  • 85
ASm
  • 379
  • 4
  • 10
  • 20
  • right now this function doesn't return anything. what is a `Stack`, and what does `push` do? – abcd May 06 '15 at 04:02
  • 1
    Why do you think the approach is wrong? – Stefan Pochmann May 06 '15 at 04:02
  • 1
    @dbliss I'm using a stack (abstract data type) and have a class that uses the push and pop methods to add/remove elements from the stack. – ASm May 06 '15 at 04:05
  • @ Stefan Pochmann Mostly because i'm not sure how to return the correct value. – ASm May 06 '15 at 04:07
  • if there are only ever two operands, and if they are always single digits, then i think what you have now works. just return `plus`. – abcd May 06 '15 at 04:09
  • @dbliss I want to be able to handle more than that if possible, at the very least multiplication and division. – ASm May 06 '15 at 04:14
  • you can handle multplication and division. `plus` is a bad name for the variable, because it holds the result not just of addition, but of any operation. don't you see that in the code? i should have added to my previous comment: this code will work only if there's only one operator per expression. – abcd May 06 '15 at 04:16

4 Answers4

6

You have a few problems:

  1. You are discarding the value after you come across an operator. To fix this you have to push the result of any operator back to the stack and then proceed to the next step.
  2. You do not skip the rest of the logic when come across a number (it is not going to make your code return a wrong answer, but still is not very smart)
  3. Your function does not return anything.

Something like this should work:

def eval_postfix(text):
    s = list()
    for symbol in text:
        if symbol in "0123456789":
            s.append(int(symbol))

        plus = None
        elif not s.is_empty():
            if symbol == "+":
                plus = s.pop() + s.pop()
            elif symbol == "-":
                plus = s.pop() - s.pop()
            elif symbol == "*":
                plus = s.pop() * s.pop()
            elif symbol == "/":
                plus = s.pop() / s.pop()
        if plus is not None:
            s.append(plus)
        else:
             raise Exception("unknown value %s"%symbol)
    return s.pop()
Hosane
  • 915
  • 9
  • 19
  • 1
    nice. your answer made me realize that intermediate results should be pushed to the stack. does my answer look right? – abcd May 06 '15 at 04:44
  • 1
    Did you switch to `list` so you and others could test it? You could use `class Stack(list): push = list.append` for that. Just saying because I like that you tried to not change much. – Stefan Pochmann May 06 '15 at 05:07
  • Yeah, but basically I have always used lists as stacks for the past few years. However your suggestion makes code more readable. :) – Hosane May 06 '15 at 05:42
  • i think the OP's `push` may add the item to the *front* of the stack, though, not the end, as `append` does. the OP hasn't specified, so we can't be sure . . . – abcd May 06 '15 at 06:40
  • Yes, push adds the item to the front of the list, I was using a stack because you cannot use the append method to a stack, however using the append method on a list does the job just as well, thank you very much @Hosane – ASm May 06 '15 at 20:19
  • This solution (and the original question) don't appear to handle multiple-digit numbers? IE 47 would be read as 4, 7 – Locane Oct 16 '18 at 03:01
  • yeah you need to just replace the fifth line with a loop that collects all the numbers and calculates the base 10 value. – Hosane Oct 16 '18 at 23:58
3

Here is a solution that may work for you. I've tried to change your code as little as possible.

Change #1: Rather than check whether symbol is between 0 and 9, you might simply try to convert symbol (which starts as a string) to an int. If that succeeds, you can treat symbol as an operand. (This allows your code to handle multi-digit numbers.)

Change #2: Raise an error if a non-number, non-operator occurs in text. (You don't want anything else to be in there.)

Change #3: Use eval instead of writing out each of the operators. eval comes with a lot of safety concerns, but I think here, since we're making sure everything is a number or an operator, we're OK.

Change #4: Push intermediate results into the stack.

Change #5: Return s.pop() after the list has been exhausted. You might want to add a line that confirms that s contains just one value at this point.

Caveat: Note that this code assumes that s will contain two values every time an operator is encountered. You might want to catch the error that would be raised if this were not true with another try/except pair of statements.

def evalPostfix(text):
    s = Stack()
    for symbol in text:
        try:
            result = int(symbol)
        except ValueError:
            if symbol not in '+-*/':
                raise ValueError('text must contain only numbers and operators')
            result = eval('%d %s %d' % (s.pop(), symbol, s.pop()))
        s.push(result)
    return s.pop() 
abcd
  • 10,215
  • 15
  • 51
  • 85
  • 1
    I think *"I've tried to change your code as little as possible"* is a lie :-P – Stefan Pochmann May 06 '15 at 05:00
  • Ha, yeahhh, that was my goal at the start, anyway. – abcd May 06 '15 at 05:04
  • I don't really like the exception approach. You can also use the symbol.isdigit() method to figure that out if you are only talking about single character numbers (which is the case here). – Hosane May 06 '15 at 05:48
  • `eval` *is* [a bad idea](https://eev.ee/blog/2012/03/24/on-principle/) here, because `eval` is a bad idea everywhere. Also, even if `eval` was reasonable, using Python’s parser to evaluate arithmetic completely misses the point of writing an expression evaluator from scratch. – 9999years Feb 06 '18 at 05:54
1

Hosane has provided a nicely detailed answer to your question, but there's one error that i think i see, although i'll admit im not an expert on this subject.

Since you're using pop your calculation goes like this. (last number in stack) (operator) (second last number in stack) for example if you have ["3","2","+"] in your list, you get 3+2

this is fine for addition or multiplication, but if you take division or substraction this would result in the wrong answer. for example (3-2) in post fix would be [3,2,-]. Your code would calculate this as (2-3) when it should have been (3-2).

So you should change the division and substraction if cases to;

elif symbol=="-":
        s.append(-stack.pop() + stack.pop())
elif symbol=="/":
        s.append(1/stack.pop() * stack.pop())
0

Working code that is translation of K&R C example,

def eval_postfix(text):

    stack = []
    tokens = text.split(" ")

    for token in tokens:

        if token.strip() == '':
            continue 

        elif token == "+":
            stack.append(stack.pop() + stack.pop())

        elif token == "-":
            op2 = stack.pop() 
            stack.append(stack.pop() - op2)

        elif token == '*':
            stack.append(stack.pop() * stack.pop())

        elif token == '/':
            op2 = stack.pop()
            if op2 != 0.0:
                stack.append(stack.pop() / op2)
            else:
                raise ValueError("division by zero found!")

        elif (is_number(token)):
                stack.append(float(token))

        else:
            raise ValueError("unknown token {0}".format(token))


    return stack.pop()
rjha94
  • 4,292
  • 3
  • 30
  • 37