This is my first program I can call 'something'. I would be glad to hear what you think of it, so I wouldn't pick up bad habits further on.
It catches errors, but requires numbers to be separated from operators. It ignores a lot of characters, therefore (+ 2 2)
is as valid as (+ 2 blargh 2)
100 lines of code + comments that should make everything clear:
# This program evaluates an arithmetic problem written as an S-expression.
# Thus 3 + (7 * 4 * 2) + 1 + (6 / 5) + 8 becomes (+ 3 (* 7 4 2) 1 (/ 6 5) 8).
# IMPORTANT NOTE: every number must be separated from an arithmetic operator by
# white space to avoid confusion between arithmetic operation and indicator of
# positiveness or negativeness of the number.(+3 7) is INVALID and evaluates to
# 7 because '(+3' bit is simplified to '+'. Instead use (+ 3 7) or (+ +3 +7)!
# The program loop.
def mainLoop():
while True:
expression = getExpression()
if (expression.upper() == "EXIT"):
break
expression = formatExpression(expression)
result = evaluate(expression, 0)[0]
print (result)
# Asks user to enter an expresion and returns the input.
def getExpression():
print("Enter \"EXIT\" to exit. Numbers must be separated from operators.")
expression = input("Enter expression: ")
return expression
# Formats an expression so it would be easy to be manipulated further on.
def formatExpression(expression):
exp = []
expression = leaveCertainChars(expression, ")0123456789.+-*/ eE")
expression = detachBrackets(expression)
expression = expression.split()
for item in expression:
if isNumber(item):
exp.append(item)
else:
for character in item:
if isOperator(character) or character == ')':
exp.append(character)
return exp
# Leaves only the characters that really matter.
def leaveCertainChars(string, chars):
new_string = ""
for character in string:
if character in chars:
new_string += character
return new_string
# Converts each ')' to ' )'
def detachBrackets(string):
new_string = ""
for character in string:
if character == ')':
new_string += " )"
else:
new_string += character
return new_string
# THE ALGORITHM:
# There are 3 types of data: operators, operands and the END symbol ')'. Each
# expression starts with an operator, has any number of operands and ends with
# ')'. There are two possible errors to occur: division by zero and missing ')'.
# Thus, for each operator, collect operands until ')' and calculate the result.
# If an operator instead of an operand is encountered in the process of operand
# collection simply put the result of evaluation of THAT expression in the place
# of missing operand and continue forward from the end of THAT expression.
# Evaluate an expression. Return result AND the index of ending bracket
def evaluate(exp, i):
operator = exp[i]
(operands, indexOfBracket) = findOperands(exp, i+1)
result = compute(operator, operands)
return (result, indexOfBracket)
# Search for operands until ')'. Skip ')'s of other expressions. Put the result
# of nested expression as just another operand and continue forward from the end
# of that nested expression.
def findOperands(exp, i):
operands = []
index = i
while exp[index] != ')':
if isNumber(exp[index]):
operands.append(exp[index])
else:
(new_value, new_index) = evaluate(exp, index)
operands.append(new_value)
index = new_index
# The key part. Incrementing index to avoid starting at ')' next time.
index += 1
# Each expression must have ')' at the end. Otherwise it's an error!
if index >= len(exp):
operands.append('YOU MISSED A BRACKET SOMEWHERE!')
break
return (operands, index)
# Returns True if string is an operator otherwise returns False.
def isOperator(string):
if string == '+' or string == '-' or string == '*' or string == '/':
return True
else:
return False
# Returns True if string can be converted into number otherwise returns False
def isNumber(string):
try:
float(string)
return True
except ValueError:
return False
# Computes a value or reports an error
def compute(operator, operands):
# If any error was encountered before it will be passed forward immediately
for operand in operands:
if not isNumber(operand):
return operand
# The default value of zero operands is zero.
if len(operands) == 0:
return '0'
# The default value of one operand is that operand
elif len(operands) == 1:
return operands[0]
# Amount of possible operators is infinite (though amount of ASCII symbols
# is limited). Extensions could include root, power, etc...
else:
result = float(operands[0])
if operator == '+':
for operand in operands[1::]:
result += float(operand)
elif operator == '-':
for operand in operands[1::]:
result -= float(operand)
elif operator == '*':
for operand in operands[1::]:
result *= float(operand)
elif operator == '/':
for operand in operands[1::]:
# Division by zero error
if float(operand) == 0:
return "DIVISION BY ZERO"
else:
result /= float(operand)
# What comes as a string, leaves as a string
return str(result)
# Don't forget to run the program!
mainLoop()