0

I'm making a calculator for a class assignment and my program works but I want it to be more efficient / well designed. I'd like to be able to create a 'template' function for a calculation but right now my code is quite linear in that I've repeated the 'calculating' segments to account for every possible equation. So far my program allows for up to 3 numbers and 2 operators. Here's my code:

    if operator1 == "+":
      if operator2 == "+":
        prevInput =  (num1 + num2 + num3)
      elif operator2 == "-":
        prevInput = (num1 + num2 - num3)
      elif operator2 == "/":
        prevInput = (num1 + (num2 / num3))
      elif operator2 == "x" or operator2 == "*":
        prevInput = (num1 + (num2 * num3))
        
    elif operator1 == "-":
      if operator2 == "+":
        prevInput = (num1 - num2 + num3)
      elif operator2 == "-":
        prevInput = (num1 - num2 - num3)
      elif operator2 == "/":
        prevInput = (num1 - (num2 / num3))
      elif operator2 == "x" or operator2 == "*":
        prevInput = (num1 - (num2 * num3))
  
    elif operator1 == "/":
      if operator2 == "+":
        prevInput = ((num1 / num2) + num3)
      elif operator2 == "-":
        prevInput = ((num1 / num2) - num3)
      elif operator2 == "/":
        prevInput = (num1 / (num2 / num3))
      elif operator2 == "x" or operator2 == "*":
        prevInput = (num1 / (num2 * num3))
  
    elif operator1 == "x" or operator1 == "*":
      if operator2 == "+":
        prevInput = ((num1 * num2) + num3)
      elif operator2 == "-":
        prevInput = ((num1 * num2) - num3)
      elif operator2 == "/":
        prevInput = (num1 * (num2 / num3))
      elif operator2 == "x" or operator2 == "*":
        prevInput = (num1 * (num2 * num3))
  
  elif not(num2 == ""):
    num1, num2 = float(num1), float(num2)
    
    if operator1 == "+":
      prevInput = (num1 + num2)
    elif operator1 == "-":
      prevInput = (num1 - num2)
    elif operator1 == "/":
      prevInput = (num1 / num2)
    elif operator1 == "x" or operator1 == "*":
      prevInput = (num1 * num2)

FYI this part is within a function itself and the prevInput variable is printed at the end of the function, but I believe the embedding of another function could be utilised in this case. Any suggestions with how I could create a default calculation template? or am I stuck with my linear format? Sorry if this seems simple, I'm only a freshman in CS.

Jarryd
  • 1
  • Is this in the context of an assignment that imposed the limitation of 3 numbers/2 operators? If not, you should look into what parsing involves. [This thread](https://stackoverflow.com/questions/2371436/evaluating-a-mathematical-expression-in-a-string) has an answer that show how parsing can be done using pyparsing. Also, understanding what an [ast](https://en.wikipedia.org/wiki/Abstract_syntax_tree) is would be useful, and in Python it is possible to [leverage the builtint `ast`](https://stackoverflow.com/questions/43389684/) module to deal with this parsing. – metatoaster Jun 26 '20 at 04:10
  • You may want to have a look at [the shunting-yard algorithm](https://en.wikipedia.org/wiki/Shunting-yard_algorithm). – Ture Pålsson Jun 26 '20 at 04:11

3 Answers3

0

The first thing you can do to improve your code is to use a loop. Simply repeatedly check for input, determine whether or not you are expected an operator or a number, and act accordingly. You can then maintain a running result of the calculation and have the ability to take in equations of any length. Your code might look something like this (this is Python "pseudocode"):

while user_is_not_done_inputting:
    if expected_operator and user_input is a valid operator:
         current_operator = operator
    elif expected_number and user_input is a valid number:
         result = apply_operator(result, operator, user_input)

    check_for_next_user_input()

if expecting_operator:
     print("ERROR: Unfinished Equation")
else:
     print(result)

This way, you only have to actually write the calculation logic once (you don't even need a function although one would be more idiomatic and elegant). The same applies to other aspects of your program (I would write functions but that is up to you).

Note: result, user_input, and current_operator would need to exist outside the loop.

ComedicChimera
  • 466
  • 1
  • 4
  • 15
  • How would I repeatedly check for an input? prior to the code I sent there is a segment validating it and filtering out unwanted characters (e.g abcd) Also, how would I actually perform the calculation if I don't know how many operators I'll be given? Thanks for the advice though, I'm curious. – Jarryd Jun 26 '20 at 04:14
  • Assuming you want your user input to be on the same line, you could have the user input a starting string (before entering the loop) that represents the whole equation and then the `check_next_user_input` function would simply take the next token (ie. number or operator) from that string until it runs out of tokens. When it does, just set the flag indicating that the user input has been fully consumed. Alternatively, you could return `None` from the user input function if the input string has been fully processed and then check in your loop header whether or not the input is `None`. – ComedicChimera Jun 26 '20 at 16:48
0

Depending on the requirements of the assignment, the easiest way would be to use eval(), like this:

x = input("Enter expression: ")
print(eval(x))

eval() takes a string as an input and evaluates the expression the string represents, so this method will work for any amount of operators as long as it's entered like python code.

Rilazy
  • 88
  • 8
  • I would use eval() but the software we are using in class hasn't implemented it yet. It was my first thought to use eval() after some validation to improve safety. – Jarryd Jun 26 '20 at 04:29
  • Fair enough, the solution would be more complicated in that case, good luck with your assignment! – Rilazy Jun 26 '20 at 04:35
0

If you just want to loop through operators an numbers, you can do that pretty simply with a lookup and a loop. The lookup maps a symbol to a function, then look them up and call them. Something like:

import operator

ops = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.truediv
}

operats = ['+', '*', '+', '+']
nums = iter([2, 6, 10, 2, 4])

res = next(nums, 0)
for n, o in zip(nums, operats):
    res = ops[o](res, n)

print(res)
# 86

This of course assumes you want all the operations in the order defined rather than some predefined operator precedence.

Mark
  • 90,562
  • 7
  • 108
  • 148
  • Thanks, this makes vague sense to me. Would I append the user's input to *operats* and *nums*? Also, what is *n* and *o*, are they just placeholders? And what does the *next(nums, 0)* accomplish? tysm again. – Jarryd Jun 26 '20 at 04:51
  • I just tried out your code, its brilliant! Could you explain how it works? I understand the assignment of +-*/ with values, but what does the *res = next(nums, 0)* and *for n, o in zip(nums, operats): res = ops[o](res, n)* do in detail? I understand zip(), but I don't fully comprehend it's application. Thank you so much anyways – Jarryd Jun 26 '20 at 05:13
  • @Jarryd, `next()` takes the next value from the iterator `nums`. You can think of this as priming the pump to get the starting value for `res`. Then you iterate though the rest of `nums` in the `for` loop. `zip(nums, operats)` pairs one number with one operator symbol and passes them to `n` and `o` respectively. The easiest way to understand this is to put some strategically-placed `print()` statements and see what the values are. – Mark Jun 26 '20 at 16:26
  • how should I set an order of precedence in this case? Since, I would need to order them according to the precedence prior to their calculation, right? – Jarryd Jul 23 '20 at 09:37
  • @Jarryd if you need to support that you’ll want to look at something like [Shunting Yard](https://en.m.wikipedia.org/wiki/Shunting-yard_algorithm) as mentioned in the comments. Or more generally, [precedence parsing](https://en.m.wikipedia.org/wiki/Operator-precedence_parser) – Mark Jul 23 '20 at 14:08