I wrote a function which gets two numbers and an operation (string) and returns the result of the two numbers with the given operation. For example calculate_matehamatical_expression(5,6,'+') should return 11. I divide the the assignment to little functions, but when I call these little function it always returns None. Can someone explain to me why that happens? This is the Code I wrote:
def mathematical_sum(num1,num2):
return num1 + num2
def mathematical_difference(num1,num2):
return num1 - num2
def mathematical_product(num1,num2):
return num1 * num2
def mathematical_division(num1,num2):
if num2 != 0:
return num1 / num2
else:
return None
def operation_error(operation):
if operation != "+" or operation != "-" or operation != "*" or operation != "/":
return None
def calculate_mathematical_expression(num1,num2,operation):
if operation == "+":
mathematical_sum(num1,num2)
elif operation == "-":
mathematical_difference(num1,num2)
elif operation == "*":
mathematical_product(num1,num2)
elif operation == "/":
mathematical_division(num1,num2)
else:
operation_error(operation)
You need to return again inside calculate_mathematical_expression, e.g.:
def calculate_mathematical_expression(num1,num2,operation):
if operation == "+":
return mathematical_sum(num1,num2)
The return in mathematical_sum doesn't affect the function it's being called from.
Your calculate_mathematical_expression function is not returning anything. Try following code:
def calculate_mathematical_expression(num1,num2,operation):
if operation == "+":
return mathematical_sum(num1,num2)
elif operation == "-":
return mathematical_difference(num1,num2)
elif operation == "*":
return mathematical_product(num1,num2)
elif operation == "/":
return mathematical_division(num1,num2)
else:
return operation_error(operation)
You need to return
When you return from a function, it only returns to the function that called it. So when you return in mathematical_sum(), the value is returned to calculate_mathematical_expression() & you need to return again from this function, like this:
if operation == "+":
return mathematical_sum(num1,num2)
elif operation == "-":
return mathematical_difference(num1,num2)
elif operation == "*":
return mathematical_product(num1,num2)
elif operation == "/":
return mathematical_division(num1,num2)
else:
return operation_error(operation)
...otherwise calculate_mathematical_expression() returns None.
operation_error() does not work
Use and instead of or. Otherwise your condition will always be True
Return a boolean, not None. Here, your function always return None
Example:
def operation_error(operation):
return operation != "+" and operation != "-" and operation != "*" and operation != "/"
You don't need operation_error()
Since you have a condition for each operator, you don't need the operation_error() function, you can directly do this:
else:
return None
...or even remove the else statement and let calculate_mathematical_expression() automatically return None when reaching its end.
Related
I am trying to make a calculator that doesn't use eval() and take the whole equation as a string. Also I can't use any libraries, modules, etc. I've managed to make something but I can't figure out how to make it to first do the multiplication and division and then the rest.
It doesn't give me an error but it does this:
4 + 4 = 8
4 + 4 * 4 = 20
4 + 4 * 4 + 4 = 4
Edit: So I found where the problem was, but it still doesn't function right. The problem was that the code doesn't even reach the part where it finds the "*". I'll mark with an arrow where it doesn't reach it.
Here is the code and thanks in advance:
print("Calculator")
# All the numbers
def numbers(num):
return(
num == "0"
or num == "1"
or num == "2"
or num == "3"
or num == "4"
or num == "5"
or num == "6"
or num == "7"
or num == "8"
or num == "9"
)
# All the operators
def operator(oprt):
return(
oprt == "+"
or oprt == "-"
or oprt == "*"
or oprt == "/"
)
# Removes all the spaces
def remove_spaces(string):
string = string.replace(" ", "")
return string
# Does the math between two numbers
def operation(string, num1, num2):
if string == "+":
return num1 + num2
if string == "-":
return num1 - num2
if string == "*":
return num1 * num2
if string == "/":
return num1 / num2
# Tests how long the number is
def test_number(numstr):
n = 0
num = ""
try:
while numbers(numstr[n]):
num += numstr[n]
n += 1
except:
pass
return(int(num), n)
# Solves the equation
def eval_equation(eq):
negative = False
# Removes the spaces
eq = remove_spaces(eq)
while True:
try:
# Checks if the first number is negative
if eq[0] == "-":
negative = True
eq = eq[1:]
# Solves the multiplication first
i = 0
eqTemp = ""
if "*" in eq:
try:
while i < len(eq):
if eq[i] == "+" or eq[i] == "-" or eq[i] == "/":
eqTemp = eqTemp + eq[:i + 1]
print(f"eqTemp = {eqTemp}")
eq = eq[i + 1:]
print(f"eq = {eq}")
elif eq[i] == "*": # <-------this is the problem----
break
print(f"eqTemp = {eqTemp}")
print(f"eq = {eq}")
i += 1
except:
i = 0
# Checks the lenght of the number
number1 = test_number(eq)[0]
# Returns the "-" in the negative number
if negative == True:
number1 = -number1
negative = False
# Removes the first number from the string to continue with the next
endNumber1 = test_number(eq)[1]
eq = eq[endNumber1:]
# Checks for any more numbers
if eq == "":
return number1
# This is the operator sign
op = eq[0]
eq = eq[1:]
# Repeats the same process with the other number
number2 = test_number(eq)[0]
endNumber2 = test_number(eq)[1]
result = operation(op, number1, number2)
# Takes the result and passes it as to the first number along with the rest of the equation
number1 = result
eq = str(number1) + eq[endNumber2:]
eq = eqTemp + eq
except Exception as error:
print(error)
break
return number1
# Main function
if __name__ == "__main__":
while True:
equation = input(": ")
print(eval_equation(equation))
Edit: Sorry, gotta get back to work, wasn't able to figure out your exact problem. I would suggest starting over on the eval_equation function as it's quite messy, and will be prone to bugs if you continue adding to it.
Unrelated to the actual question, here's an example of how you might improve a couple of functions a bit. Note the use of in, the docstrings, as well as a more clear function name.
def is_num(num):
"""Determine if the character is a number."""
return num in '0123456789'
def is_oprt(oprt):
"""Determine if the character is an operator."""
return oprt in '+-*/'
Your test_number function also can be optimised a little by iterating over the string rather than using while with try/except. It's a bit more personal preference but this is how I'd do it:
def get_num(numstr):
"""Gets the number at the start of a string."""
num = []
for i, c in enumerate(numstr):
if not is_num(c):
break
num.append(c)
try:
return int(''.join(num)), i + 1
except ValueError:
return 0, 0
One other syntax suggestion:
number2 = test_number(eq)[0]
endNumber2 = test_number(eq)[1]
Is the same as:
number2, endNumber2 = test_number(eq)
So I fixed it. I'm not sure how... but I fixed it. I just need to figure out how to add variables to the equation now :) . Here is the code and thanks to everybody who gave ideas:
print("Calculator")
# All the numbers
def numbers(num):
return num in {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
# All the operators
def operator(oprt):
return oprt in {"+", "-", "*", "/"}
# Removes all the spaces
def remove_spaces(string):
string = string.replace(" ", "")
return string
# Does the math between two numbers
def operation(string, num1, num2):
if string == "+":
return num1 + num2
if string == "-":
return num1 - num2
if string == "*":
return num1 * num2
if string == "/":
return int(num1 / num2)
# Tests how long the number is
def test_number(numstr):
n = 0
num = ""
try:
while numbers(numstr[n]):
num += numstr[n]
n += 1
except:
pass
return(int(num), n)
# Solves the equation
def eval_equation(eq):
negative = False
# Removes the spaces
eq = remove_spaces(eq)
while True:
try:
# Checks if the first number is negative
if eq[0] == "-":
negative = True
eq = eq[1:]
# Solves the multiplication first
i = 0
eqTemp = ""
if "*" in eq:
try:
while i < len(eq):
if eq[i] in {"+", "-", "/"}:
eqTemp = eqTemp + eq[:i + 1]
#print(f"eqTemp = {eqTemp}")
eq = eq[i + 1:]
#print(f"eq = {eq}")
pass
if numbers(eq[i]) == True:
pass
if eq[i] == "*":
break
i += 1
except IndexError:
i = 0
# Solves the division first
i = 0
eqTempDiv = ""
if "/" in eq:
try:
while i < len(eq):
if eq[i] in {"+", "-", "*"}:
eqTempDiv = eqTempDiv + eq[:i + 1]
#print(f"eqTemp = {eqTemp}")
eq = eq[i + 1:]
#print(f"eq = {eq}")
pass
if numbers(eq[i]) == True:
pass
if eq[i] == "/":
break
i += 1
except IndexError:
i = 0
# Checks the lenght of the number
number1 = test_number(eq)[0]
# Returns the "-" in the negative number
if negative == True:
number1 = -number1
negative = False
# Removes the first number from the string to continue with the next
endNumber1 = test_number(eq)[1]
eq = eq[endNumber1:]
# Checks for any more numbers
if eqTempDiv == "":
if eqTemp == "":
if eq == "":
return number1
# This is the operator sign
op = eq[0]
eq = eq[1:]
# Repeats the same process with the other number
number2 = test_number(eq)[0]
endNumber2 = test_number(eq)[1]
result = operation(op, number1, number2)
# Takes the result and passes it as to the first number along with the rest of the equation
number1 = result
eq = str(number1) + eq[endNumber2:]
eq = eqTemp + eqTempDiv + eq
#print(f"eqTemp final = {eqTemp}")
#print(f"eq final = {eq}")
except Exception as error:
print(error)
break
return number1
# Main function
if __name__ == "__main__":
while True:
equation = input(": ")
print(eval_equation(equation))
I have created the following pop function:
def pop(arr):
if not isempty(arr):
topelement = arr[len(arr)-1]
arr.remove(topelement)
return topelement
And it worked correctly until I used it in order to reverse order of a stack of numbers and operators:
"3 6 2 + * 14 3 4 + + /"
into
"/ + + 4 3 14 * + 2 6 3".
In first iteration of while loop shown below it pushed "/" operator to the auxiliary stack and deleted it from the top of entry, which is OK - but in the second iteration it deleted "+" sign from the middle of the entry instead of the plus sign with the highest index.
def evaluation(eq):
entryunsplit = errpostfix(eq)
entry = entryunsplit.split()
lengthofentry = len(entry)
stack = []
while len(stack) != lengthofentry:
push(stack,pop(entry))
Could someone please explain me why didn't it delete the last element and how can I avoid that error?
I am putting the whole code below in case some other element turns out to be significant.
stack1 = []
max_size = 30
def push(arr,a):
if len(arr) < max_size:
arr.append(a)
def isempty(arr):
if len(arr) == 0:
return True
else:
return False
def top(arr):
if not isempty(arr):
return arr[len(arr)-1]
def pop(arr):
if not isempty(arr):
topelement = arr[len(arr)-1]
arr.remove(topelement)
return topelement
def errpostfix(eq):
entry = eq.split()
opstack = []
exit = []
priorities = { "+": 1, "-": 1, "*": 0, "/": 0 }
for token in entry:
if token == "(":
push(opstack,token)
elif token == "*" or token == "/" or token == "+" or token == "-":
if top(opstack) == "*" or top(opstack) == "/" or top(opstack) == "+" or top(opstack) == "-":
while not isempty(opstack) and priorities[top(opstack)] >= priorities[token]:
push(exit,pop(opstack))
isempty(opstack)
push(opstack,token)
elif token == ")":
while top(opstack) != "(":
push(exit,pop(opstack))
pop(opstack)
else:
push(exit,token)
while not isempty(opstack):
push(exit, pop(opstack))
output = " ".join(exit)
return output
def isop(ch):
if ch == "+" or ch == "-" or ch == "*" or ch == "/":
return True
else:
return False
def evaluation(eq):
entryunsplit = "3 6 2 + * 14 3 4 + + /"
entry = entryunsplit.split()
lengthofentry = len(entry)
stack = []
while len(stack) != lengthofentry:
push(stack,pop(entry))
The remove() operation on a list will delete the first occurrence of an item in that list, is not "random" (check the docs). If there are several repeated elements, the first one encountered will be the one that gets deleted. To delete the last element, simply use the built-in pop() method:
def pop(arr):
if not isempty(arr):
topelement = arr[-1]
arr.pop()
return topelement
So I am currently learning about postfix, prefix, and infix expressions; and one of the challenge questions was about evaluating infix expressions using stacks. I think I understand the concept of it. However, I am really puzzled as to why my operators are not pushing onto my stack. I believe the problem is within line 31. However, I am not sure as to why. Could someone help me find and fix the problem?
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def peek(self):
return self.items[len(self.items) - 1]
def size(self):
return len(self.items)
def infix(equation):
prec_dictionary = {"(": 0, ")": 0, "^": 4, "*": 3, "/": 3, "+": 2, "-": 2, "<": 1, ">": 1}
operator_stack = Stack()
operand_stack = Stack()
allowed_operators = "+-/*^<>"
a_list = equation.split()
for i in a_list:
if i in allowed_operators:
if operator_stack.is_empty():
operator_stack.push(i)
else:
if prec_dictionary[i] > prec_dictionary[operator_stack.peek()]:
operator_stack.push(i)
if i == "(":
operator_stack.push(i)
elif i == ")":
while operator_stack.peek() != "(":
value1 = int(operand_stack.pop())
operator = operator_stack.pop()
value2 = int(operand_stack.pop())
value = compute(value1, value2, operator)
operand_stack.push(value)
operator_stack.pop()
else:
operand_stack.push(i)
return operand_stack.pop()
def compute(number1, number2, operator):
if operator == "+":
return number1 + number2
elif operator == "-":
return number1 - number2
elif operator == "*":
return number1 * number2
elif operator == "/":
return number1 / number2
elif operator == "^":
return number1 ** number2
elif operator == "<":
return min(number1, number2)
else:
return max(number1, number2)
print(infix('2 ^ ( 1 + 3 ^ 2 )'))
I'm programming a single line calculator in python. It needs to be able to take in an expression of any length and with multiple operators. It then returns the answer while following the order of operations.
i.e. "2 / 4 - 2 * 3 + 4" and it returns -1.5
This is what I have so far
def findNextOpr(s): #DONE
#this function is used to find the next operator in the string s
if len(s)<=0 or not isinstance(s,str):
print("type mimatch error: findNextOpr")
return "type mimatch error: findNextOpr"
s = list(s) #converts s to a list
operator = ["+", "-", "/", "*"]
for i in s:
if i in operator:
return i
else:
return -1
def isNumber(s): #DONE
#this function is used to check if it's a number
#checks if string s meets the requirements
if len(s)==0 or not isinstance(s, str):
print("type mismatch error: isNumber")
return "type mismatch error: isNumber"
#strip string of all whitespace.
s = s.strip(" ")
s = s[0:] #removes minus signs but only from the front of string
try: #checks to make sure s2 is a number and doesn't have more than 1 period
s=float(s)
return True
except:
return False
def getNextNumber(expr, pos): #DONE
#checks to make sure expr and pos match the requirements. returns error if they don't
if len(expr)==0 or not isinstance(expr, str) or pos<0 or pos>=len(expr) or not isinstance(pos, int):
print("type mismatch error: getNextNumber")
return None, None, "type mismatch error: getNextNumber"
s = expr[pos:] #sets string1 equal to position that was passed
op=['-','+','*','/']
newOpr = findNextOpr(s) #newOpr equals left-most operator, calls findNext to achieve this
if newOpr in op:
if expr.find(newOpr,pos)>=pos:
oprPos=expr.find(newOpr,pos)
else:
newOpr=None
if newOpr==None:
oprPos = None
if isNumber(expr[pos:oprPos]): #checks to make sure if string is actually a #
newNumber = float(expr[pos:oprPos])
else:
newNumber = None #if it is a #, assigns value to newNumber
return newNumber, newOpr, oprPos
def exeOpr(num1, opr, num2):
#This is a simple utility function skipping type check
if opr=="+":
return num1+num2
elif opr=="-":
return num1-num2
elif opr=="*":
return num1*num2
elif opr=="/":
return num1/num2
else:
return None
def calc(expr):
#the below line checks if expr is a string
if not isinstance(expr, str) or len(expr) <= 0:
print("argument error: line A in eval_expr")
return "argument error: line A in eval_expr"
#below line creates three variables
#sets them equal to whatever is returned by the getNextNumber function
#the getNextNumber function is called. It passes the expr and integer 0
newNumber, newOpr, oprPos = getNextNumber(expr, 0)
#if newNumber is none, return error
#if newOpr is none, return newNumber
#if newOpr is add/sub set mode=add, create addResult=newNumber and mulResult = None
#if newOpr is mult/div set mode=mul, crate addResult=0 and mulResult=newNumber
if newNumber is None:
print("input formula error: line B in eval_expr")
return "input formula error: line B in eval_expr"
elif newOpr is None:
return newNumber
elif newOpr=="+" or newOpr=="-":
mode="add"
addResult=newNumber #saves # at first index to addResult if 1st operator is + or -
mulResult=None
elif newOpr=="*" or newOpr=="/":
mode="mul"
addResult=0
lastOpr = "+"
mulResult=newNumber #saves # at first index to mulResult if 1st operator is + or -
#pos and opr are created
pos=oprPos+1 #current positon
opr=newOpr #current operator
while True:
newNumber, newOpr, oprPos = getNextNumber(expr, pos)
#--- code while loop ---#
while True:
newNumber, newOpr, oprPos=getNextNumber(expr, pos)
#if expr[pos:] == " 3- 2*1":
# pdb.set_trace()
if newNumber== None and pos>=len(expr):
return "Expression error"
elif newOpr== None and mode=='add':
return exeOpr(addResult, opr, newNumber)
elif newOpr== None and mode=='mul':
return exeOpr(mulResult, opr, newNumber)
elif (newOpr== "+" or newOpr=='-') and mode=='add' :
addResult= exeOpr(addResult,opr,newNumber)
pos= oprPos+1
opr=newOpr
elif (newOpr=="*" or newOpr=='/') and mode=='add':
addResult= newNumber
lastOpr= opr
mulResult= exeOpr(addResult,newOpr, newNumber)
mode='mul'
pos= oprPos+1
opr=newOpr
elif (newOpr=="*" or newOpr=="/") and mode=="mul":
mulResult= exeOpr(mulResult, opr, newNumber)
pos= oprPos+1
opr=newOpr
elif (newOpr=="-" or newOpr=='+') and mode=="mul":
mulResult=exeOpr(mulResult,opr, newNumber)
addResult= exeOpr(mulResult, lastOpr, newNumber)
mode="add"
pos= oprPos+1
opr=newOpr
#--- end of function ---#
expr = "2 / 4 - 2 * 3 + 4"
print(calc(expr))
Everything works except the output. When I run the program I get an answer of 13.0
I know the error is in the calc function. Can anyone figure out what I'm doing wrong.
Thanks.
Your calc is not working as you expected
HINT:
def exeOpr(num1, opr, num2):
#This is a simple utility function skipping type check
if opr=="+":
print "{} + {}".format(num1, num2)
return num1+num2
elif opr=="-":
print "{} - {}".format(num1, num2)
return num1-num2
elif opr=="*":
print "{} * {}".format(num1, num2)
return num1*num2
elif opr=="/":
print "{} / {}".format(num1, num2)
return num1/num2
else:
return None
OUTPUT
2.0 / 4.0
0.5 + 4.0
2.0 * 2.0
4.0 * 3.0
12.0 - 3.0
9.0 + 4.0
13.0
The question I'm having problem on is calculating the postfix form expressions: for example, (1, 2, '+', 3, '*').
By calculating the expression using the following algorithm:
1. If the expression only contains an integer, return that integer.
2. Otherwise, maintain a stack. Loop through the tuple and push every element to the stack. If the element is an operator, pop the first two element out of the stack, calculate the result and push the result into the stack.
To illustrate, we take the example above. Initially, the stack is empty.
The test case is
calculate((1, 2, '*', 3, '-', 2, '*', 5, '+'))
3
whereas my first code was no good (hardcoded and all >< ):
def calculate(inputs):
if len(inputs) ==1:
return inputs[0]
elif len(inputs) == 5:
s= []
push_stack(s, inputs[0])
push_stack(s, inputs[1])
if inputs[2] == '*':
A = s.pop() * s.pop()
elif inputs[2] == '+':
A = s.pop() + s.pop()
elif inputs[2] == '-':
A= s.pop() - s.pop()
elif inputs[2] == '/':
A = s.pop() / s.pop()
s.clear()
s= [A]
push_stack(s, inputs[3])
if inputs[4] == '*':
A = s.pop() * s.pop()
elif inputs[4] == '+':
A = s.pop() + s.pop()
elif inputs[4] == '-':
A= s.pop() - s.pop()
elif inputs[4] == '/':
A = s.pop() / s.pop()
return A
else:
s= []
push_stack(s, inputs[0])
push_stack(s, inputs[1])
if inputs[2] == '*':
A = s.pop() * s.pop()
elif inputs[2] == '+':
A = s.pop() + s.pop()
elif inputs[2] == '-':
A= s.pop() - s.pop()
elif inputs[2] == '/':
A = s.pop() / s.pop()
s.clear()
s= [A]
push_stack(s, inputs[3])
if inputs[4] == '*':
A = s.pop() * s.pop()
elif inputs[4] == '+':
A = s.pop() + s.pop()
elif inputs[4] == '-':
A= s.pop() - s.pop()
elif inputs[4] == '/':
A = s.pop() / s.pop()
s.clear()
s= [A]
push_stack(s, inputs[5])
if inputs[6] == '*':
A = s.pop() * s.pop()
elif inputs[6] == '+':
A = s.pop() + s.pop()
elif inputs[6] == '-':
A= s.pop() - s.pop()
elif inputs[6] == '/':
A = s.pop() / s.pop()
s.clear()
s= [A]
push_stack(s, inputs[7])
if inputs[8] == '*':
A = s.pop() * s.pop()
elif inputs[8] == '+':
A = s.pop() + s.pop()
elif inputs[8] == '-':
A= s.pop() - s.pop()
elif inputs[8] == '/':
A = s.pop() / s.pop()
return A
Sorry for making you read that! Then I changed the style to
def calculate(inputs):
if len(inputs) ==1:
return inputs[0]
else:
s =[]
for i in inputs:
if type(i) == int:
return push_stack(s, i)
elif i is '*' or '/' or 'x' or '+':
A = s.pop()
B =s.pop()
know = operator(i, A, B)
C = push_stack(s, know)
return C
def operator(sign, one, two):
if sign == '*':
A = one * two
elif sign == '+':
A = one + two
elif sign == '-':
A= one - two
elif sign == '/':
A = one / two
return A
Am I getting closer to the idea and how does my code improve?
** Edit **
Using IDLE:
>>> calculate((1, 2, '*', 3, '-'))
[1]
>>> calculate((1, 2, '+', 3, '*'))
[1]
>>> calculate((1, 2, '*', 3, '-', 2, '*', 5, '+'))
[1]
which is not the answer I'm looking for. It should be 1 then 9 then 3.
There is a problem with
elif i is '*' or '/' or 'x' or '+':
which is treated as
elif (i is '*') or ('/') or ('x') or ('+'):
which is not what you want (it's always true). You can instead use something like:
elif i in ('*', '/', 'x', '+'):
Also:
if type(i) == int:
return push_stack(s, i)
You shouldn't be returning there. You simply want:
if type(i) == int:
push_stack(s, i)
Lastly, I think a better approach would be to always use the stack, and just return the top of the stack at the end of your function. This saves you from having to create a special case for 1-element arguments to calculate(). Putting this all together, something along the lines of this should work:
def calculate(inputs):
stack = []
for a in inputs:
if type(a) is int:
stack.append(a)
continue
op1, op2 = stack.pop(), stack.pop()
if a == '+':
stack.append(op2 + op1)
elif a == '-':
stack.append(op2 - op1)
elif a == '*':
stack.append(op2 * op1)
elif a == '/':
stack.append(op2 / op1)
return stack.pop()
Right now this does no error-checking (e.g. for malformed expressions), but it's easy to add.
Part of the problem, which you seem to have realized, is that you are trying to make one function do everything. If you take a look at what the code is doing, and try to separate out the responsibilities, you end up with something like the following:
There are some crucial differences between Python 2.x and 3.x. Where these differences would cause problems, it's easiest to introduce helper functions and define them appropriately for each version:
import sys
if sys.hexversion < 0x3000000:
# Python 2.x
is_str = lambda s: isinstance(s, basestring)
inp = raw_input
else:
# Python 3.x
is_str = lambda s: isinstance(s, str)
inp = input
You do a fair bit of stack maintenance; this can be avoided by making a Stack class which knows how to pop and push multiple items. (It should also help your out-of-order arguments problem; 4 2 - should be 4 - 2, not 2 - 4)
class Stack(list):
def pop_n(self, n):
"""
Pop n items off stack, return as list
"""
assert n >= 0, "Bad value {}: n cannot be negative".format(n)
if n == 0:
return []
elif n <= len(self):
res = self[-n:]
del self[-n:]
return res
else:
raise ValueError("cannot pop {} items, only {} in stack".format(n, len(self)))
def push_n(self, n, items):
"""
Push n items onto stack
"""
assert n == len(items), "Expected {} items, received {}".format(n, len(items))
self.extend(items)
Rather than having a single "do-any-operation" function, we can let each operator have its own self-contained code. This makes it much easier to change or add operators without funny side-effects. First we make an Operator class
class Op:
def __init__(self, num_in, num_out, fn):
"""
A postfix operator
num_in: int
num_out: int
fn: accept num_in positional arguments,
perform operation,
return list containing num_out values
"""
assert num_in >= 0, "Operator cannot have negative number of arguments"
self.num_in = num_in
assert num_out >= 0, "Operator cannot return negative number of results"
self.num_out = num_out
self.fn = fn
def __call__(self, stack):
"""
Run operator against stack (in-place)
"""
args = stack.pop_n(self.num_in) # pop num_in arguments
res = self.fn(*args) # pass to function, get results
stack.push_n(self.num_out, res) # push num_out values back
then we define the actual operators as
ops = {
'*': Op(2, 1, lambda a,b: [a*b]), # multiplication
'/': Op(2, 1, lambda a,b: [a//b]), # integer division
'+': Op(2, 1, lambda a,b: [a+b]), # addition
'-': Op(2, 1, lambda a,b: [a-b]), # subtraction
'/%': Op(2, 2, lambda a,b: [a//b, a%b]) # divmod (example of 2-output op)
}
Now that the support structure is in place, your evaluation function is simply
def postfix_eval(tokens):
"""
Evaluate a series of tokens as a postfix expression;
return the resulting stack
"""
if is_str(tokens):
# if tokens is a string, treat it as a space-separated list of tokens
tokens = tokens.split()
stack = Stack()
for token in tokens:
try:
# Convert to int and push on stack
stack.append(int(token))
except ValueError:
try:
# Not an int - must be an operator
# Get the appropriate operator and run it against the stack
op = ops[token]
op(stack) # runs Op.__call__(op, stack)
except KeyError:
# Not a valid operator either
raise ValueError("unknown operator {}".format(token))
return stack
and to make it easier to test, we can make it interactive:
def main():
while True:
expr = inp('\nEnter a postfix expression (or nothing to quit): ').strip()
if expr:
try:
print(" => {}".format(postfix_eval(expr)))
except ValueError as error:
print("Your expression caused an error: {}".format(error))
else:
break
if __name__=="__main__":
main()
This runs like
Enter a postfix expression (or nothing to quit): 1 2 * 3 -
=> [-1]
Enter a postfix expression (or nothing to quit): 1 2 + 3 *
=> [9]
Enter a postfix expression (or nothing to quit): 1 2 * 3 - 2 * 5 +
=> [3]
Enter a postfix expression (or nothing to quit): 5 2 /%
=> [2, 1]
If you are doing calculator, or something like that, you should use function eval().
It will return result of expression.