# Quadratic “Complete the Square” Solver in Python 3

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!~~~
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 for match in letters): # if all letters == the first one
return letters # we have our variable
else:
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)
print(terms)

# 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):
x_es.append(term)
elif bool(re.search(r"^-?[0-9\.]+\$", term)): # if the term contains only numbers
constants.append(term)
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.