class Calculator: """ Takes a string input in correct format and returns an answer. Parsing included. """ def __init__(self): self.functions = { 'EXP': Function(numpy.exp), 'LOG': Function(numpy.log), 'SIN': Function(numpy.sin), 'COS': Function(numpy.cos), 'SQRT': Function(numpy.sqrt), 'ABS': Function(numpy.abs) } self.operators = { 'PLUSS': Operator(numpy.add, strength=0), 'MINUS': Operator(numpy.subtract, strength=0), 'DELE': Operator(numpy.divide, strength=1), 'GANGE': Operator(numpy.multiply, strength=1), } # Parse text to fills this queue with RPN, # the evaluate_output_queue evaluates it to find answer self.output_queue = Queue() def calculate(self): """The running of the calculator""" print("Welcome to 'COOLCULATOR'.\n" + "Exit by pressing 'ENTER' without providing input\n" + "All operators are written in norwegian!\n" + "(E.g. '+' is written 'pluss')") equation = " " while equation != "": equation = input(">>> ") try: self.output_queue_generator(self.parse_string_to_list(equation)) answer = self.evaluate_output_queue() print(">>>", answer) except IndexError: pass def evaluate_output_queue(self): """Evaluates the RPN in the queue""" stack = Stack() while not self.output_queue.is_empty(): elem = self.output_queue.pop() if isinstance(elem, numbers.Number): stack.push(elem) elif isinstance(elem, Function): _input = stack.pop() stack.push(elem.execute(_input)) elif isinstance(elem, Operator): _input_1 = stack.pop() _input_2 = stack.pop() stack.push(elem.execute(_input_2, _input_1)) return stack.pop() def output_queue_generator(self, input_list): """ Uses the shunting yard algorithm to tak a standard list of calculations and turn it intro a list of RPN, to be calculated :param input_list: a list of numbers, parentheses, operators and functions as its elements """ self.output_queue = Queue() operator_stack = Stack() for elem in input_list: if isinstance(elem, numbers.Number): self.output_queue.push(elem) elif isinstance(elem, Function): operator_stack.push(elem) elif elem == '(': operator_stack.push(elem) elif elem == ')': stack_elem = operator_stack.pop() while stack_elem != '(': self.output_queue.push(stack_elem) stack_elem = operator_stack.pop() elif isinstance(elem, Operator): if not operator_stack.is_empty(): top = operator_stack.peek() while (top is not None) and self.precedence_calculator(top, elem): self.output_queue.push(operator_stack.pop()) if not operator_stack.is_empty(): top = operator_stack.peek() else: top = None operator_stack.push(elem) while not operator_stack.is_empty(): item = operator_stack.pop() self.output_queue.push(item) def parse_string_to_list(self, input_string): """ Parses string to be used in 'output_queue_generator' :param input_string: a user-written string to be calculated; assumed correct format :return: a string of numbers, functions and operators in a 'normal' syntax """ # Make the string uppercase with no spaces, # ready for regex; re methods input_string = input_string.replace(" ", "").upper() regex_list = ( '|'.join( self.functions.keys()), '|'.join( self.operators.keys()), r'\(', r'\)', r'\d+\.\d+', # Positive float r'-\d+\.\d+', # Negative float r'\d+', # Positive integer r'-\d+') # Negative integer regex = '|'.join(regex_list) # re.findall returns a list containing all matches matches = re.findall(regex, input_string) result = [] for match in matches: # print(match) if match in self.functions.keys(): result.append(self.functions[match]) elif match in self.operators.keys(): result.append(self.operators[match]) elif match in ('(', ')'): result.append(match) else: # It's a number or trash try: result.append(float(match)) except ValueError: pass return result @staticmethod def precedence_calculator(top, elem): """ :param top: top element of stack, can be function, operator, number :param elem: is a operator with a strength :return: if top has precedence over elem """ if isinstance(top, (numbers.Number, Function)) or top in ('(', ')'): return False if isinstance(top, Operator): return top.strength > elem.strength
class Calculator(): ''' The calculator takes a string in the form of an equation, parses it into operators, operands, functions and parentheses. It then converts from infix to reverse polish notation and evaluates the expression. ''' def __init__(self, debug=False): self.functions = { 'EXP': Function(np.exp), 'LOG': Function(np.log), 'SIN': Function(np.sin), 'COS': Function(np.cos), 'SQRT': Function(np.sqrt), 'ABS': Function(np.abs) } self.constants = {'PI': math.pi, 'TAU': math.tau, 'E': math.e} self.operators = { '+': Operator(np.add, strength=0), '~': Operator(np.subtract, strength=0), '/': Operator(np.divide, strength=1), '*': Operator(np.multiply, strength=1), } self.output_queue = Queue() self.debug = debug def calculate(self): ''' Calculate the value of the RPN-equation stored in output_queue. ''' stack = Stack() while not self.output_queue.is_empty(): elem = self.output_queue.pop() if isinstance(elem, numbers.Number): stack.push(elem) if isinstance(elem, Function): _input = stack.pop() stack.push(elem.execute(_input)) if isinstance(elem, Operator): _second = stack.pop() _first = stack.pop() stack.push(elem.execute(_first, _second)) return stack.pop() def generate_output_queue(self, input_list): ''' Converts a list of operators, functions, operands, constants and parentheses from infix notation to reverse polish notation using the shunting-yard algorithm ''' def operator_precedence(top, elem): ''' Function to determine wether to pop from op_stack ''' precedence = False precedence |= isinstance(top, Function) if isinstance(top, Operator): precedence |= top.strength >= elem.strength precedence &= top != '(' return precedence self.output_queue = Queue() op_stack = Stack() for elem in input_list: if isinstance(elem, numbers.Number): self.output_queue.push(elem) if isinstance(elem, Function): op_stack.push(elem) if isinstance(elem, Operator): if not op_stack.is_empty(): top = op_stack.peek() while top is not None and operator_precedence(top, elem): self.output_queue.push(op_stack.pop()) if not op_stack.is_empty(): top = op_stack.peek() else: top = None op_stack.push(elem) if elem == '(': op_stack.push(elem) if elem == ')': next_op = op_stack.pop() while next_op != '(': self.output_queue.push(next_op) next_op = op_stack.pop() while not op_stack.is_empty(): elem = op_stack.pop() self.output_queue.push(elem) if self.debug: print(f'\nParsed string: {input_list}') print(f'Output queue: {self.output_queue._items}\n') def parse_string_to_list(self, input_str): ''' Parse input_str into a list of operators, operands, parentheses, constants and functions, using regular expressions. Then substitute the functions and operators found with their corresponding wrapper object. Strings in the form of positive or negative integers/floats are converted to float. ''' re_parts = ( r'-?\d+\.\d+', # Floating point numbers r'-?\d+', # Integers r'\(|\)', # Parentheses r'\+|\~|\*|/|', # Operators '|'.join(self.functions.keys()), # Functions '|'.join(self.constants.keys()), # Constants ) regex = '|'.join(re_parts) # re.findall preserves the order of the matches matches = re.findall(regex, input_str.upper()) result = [] for match in matches: # Function if match in self.functions.keys(): result += [self.functions[match]] # Operator elif match in self.operators.keys(): result += [self.operators[match]] # Constants elif match in self.constants.keys(): result += [self.constants[match]] # Parentheses elif match in ('(', ')'): result += [match] # Probably a number else: try: result += [float(match)] except ValueError: pass return result