3
\$\begingroup\$

I made a formula retriever that works and allows importing of fractions.

This python code is meant for the fx-cg50 calculator's micropython, where there are a lot of functions that don't work including fractions, some mathematical functions such as math.gcd and math.isclose. So I really require some advice or coding tricks to simplify my program.

Disclaimer: I'm only an A-Level student of 16 years; consider me a beginner. I know eval is insecure, but I'm not planning on uploading this online; it's only for my personal use.

# just to make the input_checker function smaller and to eliminate repeated code
def three_variables_looper_arithmetic():
    a = input("enter a1: ")
    b = input("enter n: ")
    c = input("enter d: ")
    original = 0
    count_list = [[a], [b], [c]]
    loop = 0
    for count in count_list:
        if isinstance(count[0], str):
            original = float(eval(count[0]))
            count_list[loop][0] = original
        loop += 1
    return count_list[0][0], count_list[1][0], count_list[2][0]

# just to make the input_checker function smaller and to eliminate repeated code
def three_variables_looper_geometric():
    a = input("enter a1: ")
    b = input("enter r: ")
    c = input("enter n: ")
    original = 0
    count_list = [[a], [b], [c]]
    loop = 0
    for count in count_list:
        if isinstance(count[0], str):
            original = float(eval(count[0]))
            count_list[loop][0] = original
        loop += 1
    return count_list[0][0], count_list[1][0], count_list[2][0]


# loops through all the inputs of a given situation based on whether its arithmetic
# or not, and checks whether the input is string like "6/2" so it could evaluate it, allows input of fractions
def input_checker(arithmetic_1_geometric_2, formula_num, L):
    if arithmetic_1_geometric_2 == 1:
        if formula_num == 1:
            return three_variables_looper_arithmetic()


        elif formula_num == 2:
            return three_variables_looper_arithmetic()

        elif formula_num == 3:
            a1 = input("enter a1: ")
            b = input("enter n: ")
            c = input("enter d: ")
            original = 0
            count_list = [[a1], [b], [c], [L]]
            loop = 0
            for count in count_list:
                if isinstance(count[0], str):
                    original = float(eval(count[0]))
                    count_list[loop][0] = original
            return count_list[0][0], count_list[1][0], count_list[2][0], count_list[3][0]

    elif arithmetic_1_geometric_2 == 2:
        if formula_num == 1:
            return three_variables_looper_geometric()

        elif formula_num == 2:
            return three_variables_looper_geometric()

        elif formula_num == 3:
            a1 = input("enter a1: ")
            b = input("enter n: ")
            original = 0
            count_list = [[a1], [b]]
            loop = 0
            for count in count_list:
                if isinstance(count[0], str):
                    original = float(eval(count[0]))
                    count_list[loop][0] = original
            return count_list[0][0], count_list[1][0]

# as this code is for a calculator the a and b buttons are right beside each other, so after you find your desired result
# you enter a to stop and b to continue 
def stopper():
    stop_flag = False
    stop_or_continue = ""
    while stop_or_continue != "a" or "b":
        stop_or_continue = input("Stop?: ")
        if stop_or_continue == "a":
            stop_flag = True
            break
        if stop_or_continue == "b":
            stop_flag = False
            break
    if stop_flag:
        raise SystemExit


print("Sequence & Series Solver")

# asks whether you want to solve arithmetically or geometrically, depends on the sequence/series
while True:
    choice = ""
    choices = ["a", "b", "c", "d"]
    while choice not in choices:
        choice = input("a for arithmetic\nb for geometric\n>> ")

    if choice == "a":
        arithmetic_1_geometric_2 = 1
        choice_2 = input("a for a_nth term\nb for sum\n>> ")

        # the variable arithmetic_1_geometric_2 refers to whether the inputs are for arithmetic hence it is 1, or the inputs
        # are for geometric hence 2
        # formula_num refers to the types of formulas you'll use in sequences/series
        if choice_2 == "a":
            print("a_nth=a1+(n-1)d")
            formula_num = 1
            a1, n, d = input_checker(arithmetic_1_geometric_2, formula_num, 0)
            result = a1+(n-1)*d
            print(result)

        elif choice_2 == "b":
            print("Sn=(n/2)(2a1+(n-1)d)\nSn=(n/2)(a1+L)\nEnter x if L is unknown")
            L = input("Enter L: ")
            if L == "x":
                formula_num = 2
                a1, n, d = input_checker(arithmetic_1_geometric_2, formula_num, L)
                result = (n/2)*(2*a1+(n-1)*d)
                print(result)
            else:
                formula_num = 3
                a1, n, d, L = input_checker(arithmetic_1_geometric_2, formula_num, L)
                result = (n/2)*(a1+L)
                print(result)

    elif choice == "b":
        arithmetic_1_geometric_2 = 2
        choice_2 = input("a for a_nth term\nb for sum\nc for sum_to_inf")

        if choice_2 == "a":
            print("a_nth=a1(r)^(n-1)")
            formula_num = 1
            a1, r, n = input_checker(arithmetic_1_geometric_2, formula_num, 0)
            result = a1*(r)**(n-1)
            print(result)

        elif choice_2 == "b":
            print("Sn=(a1(1-(r)^n))/(1-r)")
            formula_num = 2
            a1, r, n = input_checker(arithmetic_1_geometric_2, formula_num, 0)
            result = (a1(1-(r)**n))/(1-r)
            print(result)

        elif choice_2 == "c":
            print("S_inf=a1/(1/r)")
            formula_num = 3
            a1, r = input_checker(arithmetic_1_geometric_2, formula_num, 0)
            result = a1/(1-r)
            print(result)

    stopper()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

For now I'll mostly pick on how you do user input.

In your first two functions, and then again in your main code, you build a complicated data structure with nested lists, only to ever need the elements in the list. Just, don't use that complicated structure.

It might make sense to write a function that asks the user for input and evaluates it if needed:

def get_input(message, type_=float, evaluate=False):
    """Ask the user for input that will be cast to `type_`, (default: float).

    WARNING: If `evaluate` is `True`, `eval` will be called on the input
    before being cast to the type.
    """
    x = input(message)
    if evaluate:
        x = eval(x)
    return type_(x)

Note that I made evaluate default to False. This way it is still always obvious that this is a potentially dangerous function and you actively need to enable it. I also added a warning regarding this in the docstring. Normally I would use type_=str as a default, but since you are mostly dealing with numerical inputs, using float might make more sense here.

You can use this function like this, as a first step:

def three_variables_looper_geometric():
    a = get_input("enter a1: ", evaluate=True)
    b = get_input("enter r: ", evaluate=True)
    c = get_input("enter n: ", evaluate=True)
    return a, b, c

At this point you can realize that the function three_variables_looper_arithmetic is almost the same as the three_variables_looper_geometric function, and that this function has a lot of repetition, so it might make sense to make a generic function for this:

def get_variables(*names):
    return [get_input(f"enter {name}: ", evaluate=True) for name in names]

Which you can then use like this:

def three_variables_looper_arithmetic():
    return get_variables("a1", "n", "d")

def three_variables_looper_geometric():
    return get_variables("a1", "r", "n")

Note that I used the relatively new f-string for easy building of the string. If your Python version does not support this, replace it with the slightly longer "enter {}: ".format(name).

This now returns a list (since it uses a list comprehension), instead of a tuple, which should not make any difference (you can still assign them via tuple unpacking, etc). It also uses tuple unpacking in the signature in order to take a variable number of arguments.

You can also use this directly in the input_checker function, making those two functions completely obsolete:

def input_checker(arithmetic_1_geometric_2, formula_num, L):
    if arithmetic_1_geometric_2 == 1:
        if formula_num in [1, 2]:
            return get_variables("a1", "n", "d")
        elif formula_num == 3:
            return get_variables("a1", "n", "d") + [L]
    elif arithmetic_1_geometric_2 == 2:
        if formula_num in [1, 2]:
            return get_variables("a1", "r", "n")
        elif formula_num == 3:
            return get_variables("a1", "n")

The input function can also be improved further if needed, for example by continuing to ask if the entered string cannot be cast to the required type, or if some validator function fails (very useful to e.g. enforce a value to lie in a certain range or be one of some choices):

from sys import stderr


def get_input(message, type_=float, validator=lambda x: True, evaluate=False):
    """Ask the user for input that will be cast to `type_`, (default: float).

    WARNING: If `evaluate` is `True`, `eval` will be called on the input
    before being cast to the type.
        """
    while True:
        x = input(message)
        if evaluate:
            x = eval(x)
        try:
            x = type_(x)
        except TypeError:
            print(x, "cannot be cast to type", type_, file=stderr)
            continue
        if not validator(x):
            print(x, "is not valid.", file=stderr)
            continue
        return x

Here are two use-cases for this, the latter of which actually appears in your code later on:

get_input("enter x", validator=lambda x: 0 <= x < 100)

choices = {"a", "b", "c"}
get_input("enter choice", type_=str, validator=choices.__contains__)
# Or, equivalently
get_input("enter choice", type_=str, validator=lambda x: x in choices)
\$\endgroup\$
1
  • \$\begingroup\$ ahh interesting thank you for your time, really helpful! \$\endgroup\$
    – Anonymous
    Commented Feb 27, 2020 at 17:58

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.