4
\$\begingroup\$

All the calculator programs I have seen so far seem long, complicated and they can only operate on two numbers. I am trying to write one that is simple, short, and it can operate on as many numbers as you want. After implementing suggestions from my previous question regarding use of operator.XXX and functools.reduce I've got a new version for review:

import operator
import functools
amount_of_numbers = int(input("How many numbers?"))
my_operator = input("Which operator (*, /, +, -)?")
if my_operator == "/":
    numbers=[float(input("Number: ")) for _ in range(amount_of_numbers)]
else:
    numbers = [int(input("Number: ")) for _ in range(amount_of_numbers)]

OPERATORS = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.floordiv
}

INIT_VALUE = {
    '-': 0,
    '/': 1.00,
    '+': 0,
    '*': 1
}
result = functools.reduce(OPERATORS[my_operator], numbers, INIT_VALUE[my_operator])
print (result)
\$\endgroup\$
3
\$\begingroup\$

After a little back and forth, we got the suggestions of the previous question implemented. However there are still some minor issues:

  • Kind of strange mixing int and float calculations – I know you got 0 when doing divisions, but that is more of a test number issue than a fault in code. As it currently stands you do float numbers for the division, but your operator is operator.floordiv which rounds of the division.

    If you want to use floats when dividing, then change to operator.div, and if you want to use integer division remove the float(input("Number: ")) ..., and let the operator.floordiv remain.

  • No need for the initializing values when using functools.reduce – The newer reduce function does what is expected when reducing, so you don't need this at all in the new code.

  • The advice to use doctest still applies – This both provides examples for use, and verifies that your code does what it is expected to do.

  • Don't use top level code, include it in main() - Move all of your code at the top level into a function, which you call from if __name__ == '__main__':, as this cleans up the code interface, allows your function to be called if imported in another file, and it looks nice.

Refactored code

When implementing all of this, and using integer division the code looks like this:

import operator
import functools
import doctest


OPERATION = {
    '+': operator.add,
    '-': operator.sub,
    '*': operator.mul,
    '/': operator.floordiv
}

def calculate(numbers=None, operator=None):
    """Apply the operator to all numbers, and print result.

    >>> calculate([1, 2, 3, 4], '+')
    10
    >>> calculate([1, 2, 3, 4], '-')
    -8
    >>> calculate([1, 2, 3, 4], '*')
    24
    >>> calculate([120, 2, 3, 4], '/')
    5
    """

    if not numbers:
        amount_of_numbers = int(input("How many numbers? "))
        numbers = [int(input("Number: ")) for _ in range(amount_of_numbers)]

    if not operator:        
        operator = input("Which operator (*, /, +, -)? ")

    result = functools.reduce(OPERATION[operator], numbers)
    print(result)

if __name__ == '__main__':
    doctest.testmod()
    calculate()

Note how this way of making it a function allows for 'proper' use as a function, whilst still allowing for user to input the missing numbers and/or operator of choice.

Another possible extension would be to add another parameter to control whether it is printing the result, or returning it for further calculations. Possibly even adding a loop around it, so you can repeat calculating other stuff?

\$\endgroup\$
  • 2
    \$\begingroup\$ Thanks @holroy will proceed editing to create the ultimate calculator \$\endgroup\$ – Joe Dec 10 '15 at 7:59
0
\$\begingroup\$

Why do you only allow floats when dividing? To me this sounds like it was used to resolve a bug where divided numbers would be rounded improperly. But that doesn't mean you shouldn't allow floats for the other operators as well. The only problem this causes is printing the pointless .0 at the end of a whole number. But you could solve that with a small function that tests if int(num) == num and then formatting the number accordingly, like this:

def format_num(num):
    """Return num as a string without unnecessary float info"""

    if int(num) == num:
        return str(int(num))
    else:
        return str(num)


print(format_num(result))
\$\endgroup\$
0
\$\begingroup\$

I would like to propose a further improvement on the already good answer of @holroy:

The calculate function should, well, calculate and should not know how to get input from the user, that should be another function (and it should not output neither):

def get_number_list_input():
    amount_of_numbers = int(input("How many numbers? "))
    return [int(input("Number: ")) for _ in range(amount_of_numbers)]

The code then looks like:

def calculate(numbers, operator):
    """Apply the operator to all numbers, and print result.

    >>> calculate([1, 2, 3, 4], '+')
    10
    >>> calculate([1, 2, 3, 4], '-')
    -8
    >>> calculate([1, 2, 3, 4], '*')
    24
    >>> calculate([120, 2, 3, 4], '/')
    5
    """
    return functools.reduce(OPERATION[operator], numbers)


if __name__ == '__main__':
    doctest.testmod()
    print(
      calculate(
        get_number_list_input(),
        input("Which operator (*, /, +, -)? ")
      )
    )
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.