4
\$\begingroup\$

After reading the definition, I made a simple Reverse Polish Notation (RPN) calculator in Python.

Originally it had just 4 operators (using import operator and a lookup table) and only did integers. After looking at some example calculations, I amended it to work on floats and added a raising to powers. Then I saw some more advanced operations here and added math functions.

Owing to the large amount of math functions available, is there an easier way to include them without having to have math.<function> in the operators lookup?

# Reverse Polish Notation calculator
# based on http://en.wikipedia.org/wiki/Reverse_Polish_notation

import math
import operator
ops = {'+':operator.add,
       '-':operator.sub,
       '*':operator.mul,
       '/':operator.div,
       '^':operator.pow,
       'sin':math.sin,
       'tan':math.tan,
       'cos':math.cos,
       'pi':math.pi}

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        pass

def calculate(equation):
    stack = []
    result = 0
    for i in equation:
        if is_number(i):
            stack.insert(0,i)
        else:
            if len(stack) < 2:
                print 'Error: insufficient values in expression'
                break
            else:
                print 'stack: %s' % stack
                if len(i) == 1:
                    n1 = float(stack.pop(1))
                    n2 = float(stack.pop(0))
                    result = ops[i](n1,n2)
                    stack.insert(0,str(result))
                else:
                    n1 = float(stack.pop(0))
                    result = ops[i](math.radians(n1))
                    stack.insert(0,str(result))
    return result

def main():
    running = True
    while running:
        equation = raw_input('enter the equation: ').split(' ')
        answer = calculate(equation)
        print 'RESULT: %f' % answer
        again = raw_input('\nEnter another? ')[0].upper()
        if again != 'Y':
            running = False

if __name__ == '__main__':
    main()

Simple/straightforward test:

enter the equation: 6 4 5 + * 25 2 3 + / -
stack: ['5', '4', '6']
stack: ['9.0', '6']
stack: ['3', '2', '25', '54.0']
stack: ['5.0', '25', '54.0']
stack: ['5.0', '54.0']
RESULT: 49.000000

Math functions test:

enter the equation: 5 8 2 15 * sin * + 2 45 tan + /
stack: ['15', '2', '8', '5']
stack: ['30.0', '8', '5']
stack: ['0.5', '8', '5']
stack: ['4.0', '5']
stack: ['45', '2', '9.0']
stack: ['1.0', '2', '9.0']
stack: ['3.0', '9.0']
RESULT: 3.000000
\$\endgroup\$
3
\$\begingroup\$
  • In Python, list is designed to grow efficiently from the end. Implementing a stack using append and pop at the end is therefore a better approach.
  • I would expect is_number to return False instead of the implicit None which usually stands for a missing value.
  • An explicit math. in front of functions makes clear where they come from. If you want to support more functions you have other things to worry about. For example, the forced conversion from degrees to radians only makes sense with certain functions.
  • You need to store explicitly the number of arguments each operator expects. Your current solution is to infer that from the length of the operator's name. That is not an extensible approach.
\$\endgroup\$
1
\$\begingroup\$

You could try something like this:

import math
x = 5
methodToCall = getattr(math, 'sin')
result = methodToCall(x)

So you basically evaluate the string and get the corresponding method.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.