0
\$\begingroup\$

I programmed a calculator with int operations. The calculator stores functions in a dictionary. Also, the calculator stores how many arguments the function takes in a dictionary.

The input format is arguments followed by module and function name. These are split by lines and spaces. Input lines that start with a # are comments and are ignored by the calculator.

In order to use a value from the previous functions, an argument can be skipped and will be inserted in the front of the functions arguments.

The code is in three modules: fn (stores the functions), int_ops (int arthmatic operations), and run.py (grabs user input and outputs answer).

import fn
from fn import key, plugin_function
from int_ops import divide, multiply, subtract, add

"""
Run Module 

__fn__:
key(module_name: str, fn_name: str) -> str 
plugin_function(module_name: str, fn_name: str, number_of_args: int, func) -> None:
main() -> None

"""


def main() -> None:
    fn.plugin_function(add, "Int", "add", 2)
    fn.plugin_function(subtract, "Int", "subtract", 2)
    fn.plugin_function(multiply, "Int", "multiply", 2)
    fn.plugin_function(divide, "Int", "divide", 2)

    lines = ["2 2 Int add", "1 Int add"]
    stack = []
    split = ""

    for line in lines:
        split = line.split(" ")
        if split[0] == "#":
            continue

        """
        ignore extra spaces and extra tabs in input 
        """
        for e in split:
            e = e.replace(" ", "")
            e = e.replace("\t", "")

        """
        module name and function name are not args passed to call 
        """
        num_current_args = len(split) - 2
        needed_num_of_args = fn.num_args[key(split[-2], split[-1])]

        """
        add args from the stack for args not stated in input 
        """
        for row in range(needed_num_of_args - num_current_args):
            split.insert(0, stack[row])
            del stack[row]

        function = fn.function[key(split[-2], split[-1])]
        output = str(function(split))
        stack.append(output)
        print(stack)


if __name__ == "__main__":
    main()




"""
Int Module

fn

divide(parts: [str]) -> int
multiply(parts: [str]) -> int
subtract(parts: [str]) -> int
add(parts: [str]) -> int
"""


def divide(parts: [str]) -> int:
    """
    :param parts: parts of divide_int cmd
    Output: 0 if divsor is 0, int division otherwise
    """
    a = int(parts[0])
    b = int(parts[0])
    if b == 0:
        return 0
    return a // b


def multiply(parts :[str]) -> int:
    """
    :param parts: parts of the multiply_int cmd, ends with this name
    Output: scalar multiplication of ints
    """
    a = int(parts[0])
    b = int(parts[1])
    return a * b


def subtract(parts:[str]) -> int:
    """
    :param parts: parts of the subract_int cmd
    Output: subtraction of two
    """
    a = int(parts[0])
    b = int(parts[1])
    return a - b


def add(parts :[str]) -> int:
    """
    :param parts: parts of the add_int cmd, ends with this name
    Output: sum as int of both number
    """
    a = int(parts[0])
    b = int(parts[1])
    return a + b
    
    """
Module containing the functions that can be run
This file has the mathmatical functions of the calculator

fn
key(module_name: str, fn_name: str) -> str
function(module_name: str, fn_name: str)
num_args(module_name: str, fn_name)
plugin_function(func, module_name: str, fn_name: str, number_of_args: int) -> None
"""


__fn__ = {}
__num_args__ = {}


def key(module_name: str, fn_name: str) -> str:
    """
    :param module_name: name of the module
    :param fn_name: name of the function
    Key used for this module
    """
    return module_name + " " + fn_name


def function(module_name: str, fn_name: str):
    """
    :param module_name: module the function is in
    :param fn_name: the name of the function
    Output: function
    """
    return __fn__[key(module_name, fn_name)]


def num_args(module_name: str, fn_name: str) -> int:
    """
    :param module_name: module of the function
    :param fn_name: name of the function
    Output: Number of parameters such as x and y (2) that the function takes
    """
    return __num_args__[key(module_name, fn_name)]


def plugin_function(func, module_name: str, fn_name: str, number_of_args: int) -> None:
    """
    :param module_name: name of the module
    :param fn_name: name of the function
    :param number_of_args: number of args the function takes (required to be str)
    :param func: the function to run
    Output: None
    """
    global __fn__
    global __num_args__
    __fn__[key(module_name, fn_name)] = func
    __num_args__[key(module_name, fn_name)] = number_of_args


\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Unless I missed a major announcement regarding very new versions of Python, this isn't a proper type hint:

def divide(parts: [str]) -> int:

To type-hint a list, you need one of:

from typing import List

def divide(parts: List[str]) -> int:  # Python 3.5+ 


def divide(parts: list[str]) -> int:  # Python 3.9+

You have another bug as well:

for e in split:
    e = e.replace(" ", "")
    e = e.replace("\t", "")

This will not change split. This puts the string returned by replace into e, then e is overwritten on the next iteration and the replaced data is lost. You'd need something like this instead:

replaced_split = [e.replace(" ", "").replace("\t", "")
                  for e in split]

Then, used replaced_split instead of split.


split.insert(0, stack[row])

You should really never insert into the front of a list, unless you have 0 performance requirements. When you insert into the front, it needs to shift every other element in the list to make room at the front (O(n); where n is the size of the list). It's much better to add to the end using append, which is amortized O(1) iirc. If it's too messy adding to the end, switch to using a dequeue which allows for constant-time inserts at both ends.


Another bug:

a = int(parts[0])
b = int(parts[0])

b should be the element at index 1.

\$\endgroup\$
1
\$\begingroup\$

I think you overengineered this solution. Below is a barebones implementation in one file. The focus here is on keeping the runtime logic as simple as possible. That is, if you wanted a more robust system for adding operations, it should ideally still compile into a simple structure like a dictionary of operations before the calculator even begins.

from collections import namedtuple

Operation = namedtuple('Operation',['func','num_params'])

OPERATIONS = {
    'Int add': Operation(
        lambda a,b: int(a)+int(b),
        2
    )
}

def main() -> None:
    lines = ["2 2 Int add", "1 Int add"]
    stack = []
    for line in lines:
        tokens = [
            token.strip()
            for token in line.split()
        ]
        *args, dtype, operator = tokens
        operation = OPERATIONS[f'{dtype} {operator}']

        stack_args_needed = operation.num_params - len(args)
        for _ in range(stack_args_needed):
            args.append(stack.pop())

        result = operation.func(*args)
        stack.append(result)
        print(stack)


if __name__ == "__main__":
    main()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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