I created this small Python calculator after abstaining from writing code for a while now. I would love seasoned programmers input.
#!/usr/bin/env python
" A Simple calculator "
class Calculator(object):
""" Example input:
expression: (12+4)*2^4-(10-3*(15/5))
steps:
1) compile it to a list of ops and numbers: [ [12,+,4],*,2,^,4,-,[10,-,3,*,[15,/,5]] ]
2) calculate starting with the highest operators first:
[ [12, +, 5], *, 16, -, [10,-,3,*,[15,/,5]] ]
[ 17, *, 16, -, [10,-,3,*,3] ]
[ 17, *, 16, -, [10-9] ]
[ 17, *, 16, -, 1]
[ 272, -, 1 ]
[ 271 ]
TODO:
* add floating point support
"""
_stack = []
" Flag that signfies if it's the first character in the expression "
INITIAL = True
" exit perenthesis "
EXIT_PE = False
" in number "
IN_NU = False
" in operator "
IN_OP = False
OPERATORS = ('+', '-', '*', '/', '^')
OP_ORDER = (('^',), ('*', '/'), ('+', '-'))
def compile(self, input_eq):
for c in input_eq:
try:
" check if its a number "
current = int(c)
self.IN_OP = False
" if it's a new digit to a previous number "
if self.IN_NU:
" add it to the previous number "
self._add_new_num(current)
else:
" it's a new number add it to stack "
self._add_new_num(current)
self.IN_NU = True
except ValueError:
self.IN_NU = False
" if it's an operator "
if c in self.OPERATORS:
if not self._stack:
raise Exception("You can't start an expression with an operator")
if self.IN_OP:
raise Exception("illegal expression")
else:
self._append_element(c)
self.IN_OP = True
elif c == '(':
self._add_new_perentheses()
self.IN_OP = True
elif c == ')':
self.EXIT_PE = True
self.IN_OP = False
else:
raise Exception("Bad input")
if self.INITIAL:
self.INITIAL = False
def _get_last_position(self):
" Returns the last inner most list in the stack "
list_ref = list_prev = self._stack
try:
" While there's a list "
while list_ref[-1] or list_ref[-1] == []:
if isinstance(list_ref[-1], list):
" make a reference to the list "
list_prev = list_ref
list_ref = list_ref[-1]
else:
break
if self.EXIT_PE == True:
self.EXIT_PE = False
return list_prev
else:
self.EXIT_PE = False
return list_ref
except IndexError:
if self.EXIT_PE == True:
self.EXIT_PE = False
return list_prev
else:
self.EXIT_PE = False
return list_ref
def _append_element(self, el):
last_pos = self._get_last_position()
last_pos.append(el)
def _add_new_num(self, num):
" if its the first character in an expression "
if not self._stack or self._get_last_position() == []:
self._append_element(num)
else:
prev_c = self._get_last_position()[-1]
" check if previous char is a number "
is_int = isinstance(prev_c, int)
if is_int:
self._add_to_previous_num(num, self._stack)
elif prev_c in self.OPERATORS:
self._append_element(num)
else:
is_list = isinstance(self._stack[-1], list)
" if it's a list search the last element in the list's children "
if is_list:
list_ref = self._get_last_position()
self._add_to_previous_num(num, list_ref)
else:
raise Exception("something is broken")
def _add_to_previous_num(self, num, stack):
try:
last_pos = self._get_last_position()
last_pos[-1] = last_pos[-1]*10+num
except IndexError:
last_pos.append(num)
def _add_new_perentheses(self):
last_pos = self._get_last_position()
last_pos.append([])
def calculate(self, expr):
self.compile(''.join(expr.split()))
result = self._rec_calc(self._stack)
" initialize the stack "
self._stack = []
return result
def _rec_calc(self, stack):
while len(stack) > 1:
for op in xrange(len(self.OP_ORDER)):
for el in xrange(len(stack)):
try:
if isinstance(stack[el], list):
result = self._rec_calc(stack[el])
del stack[el]
stack.insert(el, result)
elif stack[el] in self.OP_ORDER[op]:
result = self._calc_binary(stack, el, stack[el])
" delete all three elements that were used in the binary operation "
del stack[el-1]
del stack[el-1]
del stack[el-1]
stack.insert(el-1, result)
except IndexError:
break
else:
continue
break
return stack[0]
def _calc_binary(self, stack, index, op):
out = stack[index-1]
next = stack[index+1]
if op == '+':
out += next
elif op == '-':
out -= next
elif op == '*':
out *= next
elif op == '/':
out /= next
elif op == '^':
out **= next
return out
if __name__ == '__main__':
calc = Calculator()
print calc.calculate("12^2-(5*(2+2)))")
print calc.calculate("2*32-4+456+(1+2)+3+(1/2*3+3+(1+2))")
print calc.calculate("2 * (7+1) / (2 + 5 + (10-9)) ")
Edit: This is the modified version using Sean Perry's comments.
#!/usr/bin/env python
" A Simple calculator "
class Calculator(object):
""" Example input:
expression: (12+4)*2^4-(10-3**(15/5))
steps:
1) compile it to a list of ops and numbers: [ [12,+,4],*,2,^,4,-,[10,-,3,*,[15,/,5]] ]
2) calculate starting with the highest operators first:
[ [12, +, 5], *, 16, -, [10,-,3,*,[15,/,5]] ]
[ 17, *, 16, -, [10,-,3,*,3] ]
[ 17, *, 16, -, [10-9] ]
[ 17, *, 16, -, 1]
[ 272, -, 1 ]
[ 271 ]
TODO:
* add floating point support
"""
_stack = []
# Flag that signfies if it's the first character in the expression
INITIAL = True
# exit perenthesis
EXIST_PARENS = False
# in number
IN_NUM = False
# in operator
IN_OPERATOR = False
OPERATORS = {
'+': lambda x,y: x+y,
'-': lambda x,y: x-y,
'*': lambda x,y: x*y,
'/': lambda x,y: x/y,
'^': lambda x,y: x**y
}
OPS_ORDER = (('^',), ('*', '/'), ('+', '-'))
class ErrorInvalidExpression(Exception):
pass
def compile(self, input_eq):
"""
Compile the expression to a python representation
of a list of numbers, operators and lists (parentheses)
"""
for c in input_eq:
try:
# check if its a number
current = int(c)
except ValueError:
# its not a number
self.IN_NUM = False
# if it's an operator
if c in self.OPERATORS.keys():
if not self._stack:
raise ErrorInvalidExpression("You can't start an expression with an operator")
if self.IN_OPERATOR:
raise ErrorInValidExpression("More than one operator in a sequance")
else:
self._append_element(c)
self.IN_OPERATOR = True
elif c == '(':
self._add_new_parentheses()
self.EXITS_PARENS = False
elif c == ')':
self.EXIST_PARENS = True
else:
raise ErrorInvalidExpression("Syntax Error")
continue
# runs when its a number
self.IN_OPERATOR = False
# add the number to the stack
self._add_new_num(current)
# if its a new number
if not self.IN_NUM:
self.IN_NUM = True
if self.INITIAL:
self.INITIAL = False
def _get_last_position(self):
""" Returns the last inner most list in the stack """
list_ref = list_prev = self._stack
try:
# While there's a list
while list_ref[-1] or list_ref[-1] == []:
if isinstance(list_ref[-1], list):
# make a reference to the list
list_prev = list_ref
list_ref = list_ref[-1]
else:
break
except IndexError:
pass
if self.EXIST_PARENS == True:
self.EXIST_PARENS = False
return list_prev
else:
return list_ref
def _append_element(self, el):
last_pos = self._get_last_position()
last_pos.append(el)
def _add_new_num(self, num):
# if its the first character in an expression
if not self._stack or self._get_last_position() == []:
self._append_element(num)
else:
prev_c = self._get_last_position()[-1]
# check if previous char is a number
is_int = isinstance(prev_c, int)
if is_int:
self._add_to_previous_num(num, self._stack)
elif prev_c in self.OPERATORS.keys():
self._append_element(num)
else:
is_list = isinstance(self._stack[-1], list)
# if it's a list search the last element in the list's children
if is_list:
list_ref = self._get_last_position()
self._add_to_previous_num(num, list_ref)
else:
# this should never happen
raise Exception("A fatal error has occured")
def _add_to_previous_num(self, num, stack):
try:
last_pos = self._get_last_position()
last_pos[-1] = last_pos[-1]*10+num
except IndexError:
last_pos.append(num)
def _add_new_parentheses(self):
last_pos = self._get_last_position()
last_pos.append([])
def calculate(self, expr):
self.compile(''.join(expr.split()))
# do the actual calculation
result = self._rec_calc(self._stack)
# initialize the stack
self._stack = []
return result
def _rec_calc(self, stack):
while len(stack) > 1:
for ops in self.OPS_ORDER:
for el in xrange(len(stack)):
try:
if isinstance(stack[el], list):
result = self._rec_calc(stack[el])
del stack[el]
stack.insert(el, result)
elif stack[el] in ops:
result = self._calc_binary(stack, el)
# delete all three elements that were used in the binary operation
del stack[el-1]
del stack[el-1]
del stack[el-1]
stack.insert(el-1, result)
except IndexError:
break
else:
continue
break
return stack[0]
def _calc_binary(self, stack, index):
op = stack[index]
prev = stack[index-1]
next = stack[index+1]
for symbol, action in self.OPERATORS.items():
if symbol == op:
return action(prev, next)
if __name__ == '__main__':
calc = Calculator()
print calc.calculate("12^2-(5*(2+2)))") # 124
print calc.calculate("2*32-4+456+(1+2)+3+(1/2*3+3+(1+2))") # 528
print calc.calculate("2 * (7+1) / (2 + 5 + (10-9)) ") # 2
calculate = lambda x:eval(x)
. Works like a charm. – ebarr Apr 9 '14 at 6:51==
with booleans; useif self.EXIST_PARENS is True:
or even justif self.EXIST_PARENS:
. – alexwlchan Apr 9 '14 at 10:35