I quickly wrote this program to transform quadratic expressions in general form into vertex form.

\$ax^2 + bx + c \;=\; a(x-h)^2 + k,\quad\text{where}\quad h = -\frac{b}{2a} \quad\text{and}\quad k = c - ah^2 = c - \frac{b^2}{4a}.\$

The actual conversion is easy, but with writing a lexer/parser came pain points:

  • I tokenize with Regex, and some of the regex patterns are built dynamically using f-strings (which seems suspect).

  • I use eval to compute terms in parentheses (to make fractions easy to input, for example).

I also feel like there's probably a library for working with ASCII-fied algebraic expressions, (lots of the same kind of code populates sites like Symbolab, Cymath and Desmos,) but I do not know of it.

## Tests:
# user_in = "(16/4)x^2 7x (3**2)" -> "Result is: 4.0(x + (0.875))^2 + (5.9375)" (Passes)
# user_in = "(41*4)x^2 41x 2" -> "Result is: 164.0(x + (0.125))^2 + (-0.5625)" (Passes)
# user_in = "x^2 4" -> "Result is: (x + (2.0))^2 + (-4.0)" (Fails)
# user_in = "x^2 4x 16" -> "Result is (x + (2.0))^2 + 12.0" (Fails)
import re

print("""~~~Square Completer!~~~
Enter the terms of your quadratic equation in standard form
Addition is implied, terms separated by spaces.
Only use one kind of single-letter variable in the whole expression.
Surround computations such as constant multiplication and fractions in parens.
DO NOT put variables inside parens!""")
user_in = input("> ")

# extract the variable
def extract_variable(expression):
    letters = re.findall(r'[a-z]', expression) # all the letters
    if all(match == letters[0] for match in letters): # if all letters == the first one
        return letters[0] # we have our variable
        raise ValueError("Only use one kind of single-letter variable in the whole expression.")

variable = extract_variable(user_in)

# eval all things in parens and split string into terms
def split_into_terms(expression):
    expression = re.sub(fr"\([^{variable}\)]+\)", lambda m: str(eval(m.group())), expression)
    print(f"working with {expression}")
    return expression.split(' ')

terms = split_into_terms(user_in)

# sort terms into bins

def bin_terms(terms):
    squareds, x_es, constants = [], [], []
    for term in terms:
        term = term.strip().lower()
        if term.endswith('^2'):
            squareds.append(term[:-2]) # without trailing '^2'
        elif term.endswith(variable):
        elif bool(re.search(r"^-?[0-9\.]+$", term)): # if the term contains only numbers
    return squareds, x_es, constants

squareds, x_es, constants = bin_terms(terms)

print('unprocessed:', squareds, x_es, constants, sep='\n')

def sum_bins(squareds, x_es, constants):
    squareds = [squared[:-1] for squared in squareds if squared.endswith(variable)] # 2x -> 2
    print('squareds step1:', squareds, sep='\n')
    squareds = ['1' if squared == variable else squared for squared in squareds] # x -> 1
    print('step2:', squareds)

    print('\nprocessed:', squareds, x_es, constants, sep='\n')

    constant = sum(float(c) for c in constants)
    print('\n ***EXES: ', x_es,'***')
    x = sum(float(el[:-1]) for el in x_es)
    print('\n ***SUMX: ', x,'***')
    squared = sum(float(el) for el in squareds)

    return squared, x, constant

squared, x, constant = sum_bins(squareds, x_es, constants)

##print('\ntotals:', squared, x, constant, sep='\n')

##constant = constant / squared
##x = x / squared
##squared = 1

##print('\nover square term:', squared, x, constant, sep='\n')

def complete_square(a,b,c):
    Complete the square, transforming a quadratic
    of form
    ax^2 + bx + c
    to form
    a(x + p)^2 - q
    p = b/(2*a)
    q = c - (b**2)/(4*a)
    return p, q

p, q = complete_square(squared, x, constant)
a = squared
print(f'Result is: {a}({variable} + ({p}))^2 + ({q})')

There is a library to help with this! Check out sympy, particularly sympify and parse_expr.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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