Guest User

python_calculator.py

a guest
Jul 8th, 2019
82
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import sys
  2. import math
  3.  
  4. usage_notes = """
  5. 1. Session prompts:
  6.    1. n: New session
  7.    2. c: Current session
  8.  
  9. 2. Supported operators: +, -, *, /, ^, !
  10.  
  11. 3. Precedence:
  12.    1. Parenthesization
  13.    2. Factorial
  14.    3. Exponentiation
  15.    4. Multiplication and Divison
  16.    5. Addition and Subtraction
  17.  
  18. 4. Use of identifiers is supported. Use commas to separate them:
  19.    n: a=10,b=5
  20.    c: a+b
  21.    -> 15
  22.  
  23. 5. Result of the previous expression can accessed by using the 'r'
  24. identifier:
  25.    n: 2+3
  26.    -> 5
  27.    c: r+10
  28.    -> 15
  29.  
  30. 6. Special commands:
  31.    1. n: Stars a new session. Deletes all previous identifiers.
  32.    2. q: Quits the program
  33. """
  34.  
  35. identifiers = {}
  36.  
  37.  
  38. def start():
  39.     # Creates a prompt dictionary to differentiate sessions.
  40.     # Starts the main loop of the program.
  41.     # Takes the input from the user and calls controller().
  42.  
  43.     prompts = {
  44.         'n': 'n: ',
  45.         'c': 'c: ',
  46.     }
  47.  
  48.     prompt = prompts['n']
  49.     while True:
  50.         expr = input(prompt)
  51.         if expr == 'q':
  52.             break
  53.         elif expr == 'n':
  54.             prompt = prompts['n']
  55.             identifiers.clear()
  56.         else:
  57.             res = controller(expr)
  58.  
  59.             if res == 'e':
  60.                 print('error: invalid expression\n')
  61.             elif res == 'i':
  62.                 prompt = prompts['c']
  63.             else:
  64.                 print('-> ' + identifiers['r'] + '\n')
  65.                 prompt = prompts['c']
  66.  
  67.  
  68. def controller(expr):
  69.     # Calls create_identifiers or passes the expr to get_postfix()
  70.     # to be converted into a postfix expression list. And, calls
  71.     # postfix_eval() for evaluation. All the Exceptions
  72.     # are terminated, so the main loop keeps running.
  73.  
  74.     try:
  75.         if '=' in expr:
  76.             return create_identifiers(expr)
  77.         postfix_expr = get_postfix(expr)
  78.         return postfix_eval(postfix_expr)
  79.     except Exception:
  80.         return 'e'
  81.  
  82.  
  83. def create_identifiers(expr):
  84.     # Identifiers are implemented as a global dictionary. First,
  85.     # the string is split using ',' as a delimiter. The resulting
  86.     # substring are separated using '='. First substring is assigned
  87.     # as a key with second substring as the value.
  88.  
  89.     expr_list = expr.replace(' ', '').split(',')
  90.  
  91.     for stmt in expr_list:
  92.         var, val = stmt.split('=')
  93.         identifiers[var] = val
  94.     return 'i'
  95.  
  96.  
  97. def get_postfix(expr):
  98.     # Converts infix expressions to postfix expressions to remove ambiguity.
  99.     # Example: a+b*c -> abc*+
  100.  
  101.     # Remove all the spaces in the given expression.
  102.     expr = expr.replace(' ', '')
  103.     sep_str = ''
  104.  
  105.     # Insert spaces only around supported operators, so splitting
  106.     # can be done easily later.
  107.     for a_char in expr:
  108.         if a_char in '+-*/^!()':
  109.             sep_str += ' %s ' % a_char
  110.         else:
  111.             sep_str += a_char
  112.  
  113.     # Use the default space as the delimiter and split the string.
  114.     token_list = sep_str.split()
  115.  
  116.     # Only operators are pushed on to the op_stack, digits and identifiers
  117.     # are appended to the postfix_list.
  118.     op_stack = []
  119.     postfix_list = []
  120.    
  121.     prec = {}
  122.     prec['!'] = 5
  123.     prec['^'] = 4
  124.     prec['/'] = 3
  125.     prec['*'] = 3
  126.     prec['+'] = 2
  127.     prec['-'] = 2
  128.     prec['('] = 1
  129.  
  130.     # The current operator's precedence in the loop is compared with the
  131.     # operators in the stack. If it's higher, it's pushed on the stack.
  132.  
  133.     # If it less than or equal, the operators are popped until the
  134.     # precedence of the operator at the top is less than the
  135.     # current operators'.
  136.  
  137.     # When parentheses are used, ')' forces all the operators above '('
  138.     # to be popped.
  139.  
  140.     # Whenever an operator is popped it's appended to the postfix_list.
  141.     for token in token_list:
  142.         if isnum(token) or token.isalpha():
  143.             postfix_list.append(token)
  144.         elif token == '(':
  145.             op_stack.append(token)
  146.         elif token == ')':
  147.             top_token = op_stack.pop()
  148.             while top_token != '(':
  149.                 postfix_list.append(top_token)
  150.                 top_token = op_stack.pop()
  151.         else:
  152.             while op_stack != [] and \
  153.                 (prec[op_stack[-1]] >= prec[token]):
  154.                 postfix_list.append(op_stack.pop())
  155.             op_stack.append(token)
  156.    
  157.     while op_stack != []:
  158.         postfix_list.append(op_stack.pop())
  159.    
  160.     return postfix_list
  161.  
  162.  
  163. def postfix_eval(postfix_list):
  164.     # Similar stack based approach is used here for evaluation. If a
  165.     # identifier or digit is found, push it on the operand_stack. If
  166.     # an operator is found, use it on the last two operands or the last
  167.     # in case of '!', and append the result on the stack.
  168.  
  169.     operand_stack = []
  170.  
  171.     for val in postfix_list:
  172.         if isnum(val):
  173.             operand_stack.append(float(val))
  174.         elif val.isalpha():
  175.             val = identifiers[val]
  176.             operand_stack.append(float(val))
  177.         elif val in '+-*/^!':
  178.  
  179.             if val != '!':
  180.                 op2 = operand_stack.pop()
  181.                 op1 = operand_stack.pop()
  182.                 res = calc(op1, val, op2)
  183.                 operand_stack.append(res)
  184.             else:
  185.                 op = operand_stack.pop()
  186.                 res = math.factorial(op)
  187.                 operand_stack.append(res)
  188.  
  189.     res = operand_stack[-1]
  190.     int_res = int(res)
  191.     if int_res == res:
  192.         res = int_res
  193.  
  194.     identifiers['r'] = str(res)
  195.  
  196.  
  197. def isnum(val):
  198.     # Used as a helper function to check if the argument is a number.
  199.     try:
  200.         float(val)
  201.         return True
  202.     except Exception:
  203.         return False
  204.  
  205.  
  206. def calc(op1, op, op2):
  207.     # Performs the operation on the operands and returns the result.
  208.     if op == '+':
  209.         return op1 + op2
  210.     elif op == '-':
  211.         return op1 - op2
  212.     elif op == '*':
  213.         return op1 * op2
  214.     elif op == '/':
  215.         return op1 / op2
  216.     elif op == '^':
  217.         return op1 ** op2
  218.  
  219. if sys.argv[-1] == 'n':
  220.     print(usage_notes)
  221.    
  222. start()
RAW Paste Data