Fator e Solucionador de Expressão Quadrática
Decidi criar um programa que pode fatorar e resolver expressões quadráticas em micropython, onde a biblioteca padrão é limitada, e não tenho ideia de como implementar módulos externos nela, então tive que preparar este programa do zero.
A premissa simples é que ele pode fatorar e resolver a maioria das expressões quadráticas e exibir as soluções de forma a tornar mais fácil verificar sua solução.
Documentei parte do programa para tornar mais fácil entender o que cada parte do programa faz. Gostaria de alguns conselhos sobre como otimizar e tornar o programa mais eficiente e compacto, com base na ideia do micropython.
# quadratic factorer, and solver
from math import sqrt
def is_integer(n):
"""
checks if the float given is an integer
True - float can be an integer
False - float is not an integer
"""
return int(n) == n
def gcd(*values):
"""
finds the greatest common divisor of values
and returns the absolute value of the divisor
"""
x, *b = values
for y in b:
while y != 0:
(x, y) = (y, x % y)
return abs(x)
def isclose(a, b, tolerance):
"""
checks whether the difference between the two values are smaller or equal to the tolerance
return True - yes
return False - no
"""
return abs(a-b) <= tolerance
def fraction(a, factor=0, tolerance=0.01):
"""
Uses brute force, to turn a float into a fraction
if a is a whole number, then it is returned.
if a is a float, then the closest possible fraction to tolerance level of difference
and returns a fraction in string format.
"""
while True:
factor += 1
a_rounded = int(round(a*factor))
if isclose(a*factor, a_rounded, tolerance):
break
if factor == 1:
return a_rounded
else:
return "{}/{}".format(a_rounded, factor)
def simplify_fraction(numer, denom):
"""
simplifies a fraction, to a simpler form
"""
if denom == 0:
return None, None
# Remove greatest common divisor:
common_divisor = gcd(numer, denom)
return numer // common_divisor, denom // common_divisor
def get_determinant(a, b, c):
"""
returns the determinant of a polynomial ax^2 + bx + c
"""
return b**2 - 4*a*c
def factors(n):
"""
finds the factors of n, and returns a list of factors (unordered)
"""
return list(set(x for tup in ([i, n//i]
for i in range(1, int(sqrt(n))+1) if n % i == 0) for x in tup))
def simplify_sqrt(n):
"""
simplifies the n in sqrt(n)
and turns it into a surd
return values:
(x, y) --> xsqrt(y)
- x is the coefficient of the surd
- y is the value remaining in the sqrt
(0, y) --> sqrt(y)
(y, 0) --> y
"""
perfect_square = None
float_to_int = lambda x: int(x) if is_integer(x) else x
for factor in sorted(factors(n), reverse=True)[:-1]:
if is_integer(sqrt(factor)):
perfect_square = factor
break
if perfect_square == n:
return (int(sqrt(perfect_square)), 0)
elif perfect_square:
factor1 = sqrt(perfect_square)
factor2 = n / perfect_square
return (float_to_int(factor1), float_to_int(factor2))
else:
return (0, n)
def format_tuple_to_sqrt(A, B): # Asqrt(B)
"""
turns a tuple from simplify_sqrt to an actual string representation.
"""
if A == 0:
A = ""
elif B == 0:
return str(A)
return "{}sqrt({})".format(A, B)
def solve_completing_the_square(a, b, c):
"""
( x +- ysqrt(B) )/z
acquires the values of x, y, B, and z by reverse engineering the solutions
and returns them
"""
f = simplify_sqrt(get_determinant(a, b, c))
g = gcd(f[0], 2*a, -b)
# x, y, B, z
return -b/g, [int(f[0]/g), f[1]], (2*a)/g # x, (h[0], h[1]), z
def format_complete_the_square_solutions(x, h, z):
"""
h = (y, B) --> ysqrt(B)
acquires the x, h, and z
and formats a proper string representation for the solution using complete the square
if z is 1
then no '/1' is shown.
"""
# ( x +- h[0]sqrt(h[1]) )/z
h[0] = 0 if h[0] == 1 else h[0]
h = format_tuple_to_sqrt(*h)
if z < 0:
x, z = x*-1, z*-1
sol1 = "( {} + {} )/{}".format(int(x), h, int(z))
sol2 = "( {} - {} )/{}".format(int(x), h, int(z))
if z == 1:
return sol1[:-2], sol2[:-2]
return sol1, sol2
def solve_quadratic_equation(a, b, c):
"""
returns a tuple of solutions, if a polynomial abc, has atleast 1 solution, else returns None
formula = (-b+-sqrt(b^2-4ac))/2a
"""
# two solutions, or one solution
if get_determinant(a, b, c) >= 0:
return ( (-b+sqrt(get_determinant(a, b, c))) / (2*a), (-b-sqrt(get_determinant(a, b, c))) / (2*a)) # (x1, x2)
# no solutions
else:
return None, None
def factor_quadratic_equation(a, b, c):
"""
factors the quadratic polynomial a, b, c on multiple conditions
support when
1) c = 0
2) b = 0 (if perfect square)
3) a, b, c present
4) complete the square is involved
"""
get_sign = lambda x: "+" if x > 0 else "-" # set the sign based on x's value
flip_sign_if_negative = lambda x, sign: -x if sign == '-' else x # switch the signs for formatting if sign == '-'
float_to_int = lambda x: int(x) if is_integer(x) else x # only if the float is actually an integer like 3.0
if a < 0:
a, b, c = a/-1, b/-1, c/-1
if c == 0: # factor by gcf 6x^2 - 2x
gcf = gcd(a, b)
a, b = a/gcf, b/gcf
gcf = "" if gcf == 1 else gcf
sign = get_sign(b)
b = flip_sign_if_negative(b, sign)
return "{}x({}x{}{})".format(float_to_int(gcf), fraction(a), sign, fraction(b))
else:
denom = 2*a
x1, x2 = solve_quadratic_equation(a, b, c)
if x1 and x2:
x1_numer, x2_numer = x1*denom, x2*denom
else:
x1_numer = x2_numer = None
if (not x1 and not x2) or not (is_integer(x1_numer) and is_integer(x2_numer)) or not is_integer(denom):
# factor by completing the square 2(x+3) + 1
# (x+p)^2 + q
global completing_the_square
completing_the_square = True
if a != 1:
a, b, c = a/a, b/a, c/a
p = b/(2*a)
q = c - (b**2)/(4*a)
sign1 = get_sign(p)
sign2 = get_sign(q)
p = flip_sign_if_negative(p, sign1)
q = flip_sign_if_negative(q, sign2)
return "(x{}{})^2 {} {}".format(sign1, fraction(p), sign2, fraction(q))
else:
# normal factoring (x+3)(x+3)
x1_gcd, x2_gcd = gcd(x1_numer, denom), gcd(x2_numer, denom)
x1_numer, x2_numer = -x1_numer/x1_gcd, -x2_numer/x2_gcd
x1_denom, x2_denom = denom/x1_gcd, denom/x2_gcd
gcf = gcd(a, b, c)*a/abs(a)
sign1 = get_sign(x1_numer)
sign2 = get_sign(x2_numer)
x1_numer = flip_sign_if_negative(x1_numer, sign1)
x2_numer = flip_sign_if_negative(x2_numer, sign2)
return "{}({}x{}{})({}x{}{})".format(float_to_int(gcf) if gcf != 1 else "", fraction(x1_denom) if x1_denom != 1 else "", sign1, fraction(x1_numer), fraction(x2_denom) if x2_denom != 1 else "", sign2, fraction(x2_numer))
while True:
completing_the_square = False
a = float(input("insert a: "))
b = float(input("insert b: "))
c = float(input("insert c: "))
factored_form = factor_quadratic_equation(a, b, c)
solutions = solve_quadratic_equation(a, b, c)
print(factored_form) if factored_form else print("No Factored Form")
if solutions[0]:
if completing_the_square:
solution0_fraction, solution1_fraction \
= format_complete_the_square_solutions(*solve_completing_the_square(a, b, c))
else:
solution0_fraction = "" if is_integer(solutions[0]) else fraction(solutions[0])
solution1_fraction = "" if is_integer(solutions[1]) else fraction(solutions[1])
solution1 = "x1 = {}".format(round(solutions[0], 5)) if solution0_fraction == "" else "x1 = {} or\n{}".format(round(solutions[0], 5), solution0_fraction)
solution2 = "x2 = {}".format(round(solutions[1], 5)) if solution1_fraction == "" else "x2 = {} or\n{}".format(round(solutions[1], 5), solution1_fraction)
print(solution1)
print(solution2) if solutions[0] != solutions[1] else None
else:
print("No Solution")
stop = input("'x' to stop: ")
if stop == 'x':
break
Respostas
Existe algum lugar factor_quadratic_equationonde o valor de retorno de flip_sign_if_negative(x,sign)seja algo diferente do valor absoluto de x? Se não, eu recomendaria usar o valor absoluto, uma vez que já é uma função familiar.
Por a/-1que em vez de -a?
As partes simples são bem documentadas (embora a maioria delas seja fácil de entender mesmo sem documentação), mas existem partes complicadas com pouca ou nenhuma explicação. E não estou convencido de que você tenha pensado muito sobre o que realmente deseja factor_quadratic_equationfazer.
Você escreveu um algoritmo bastante complicado aqui. Você testou para ver se os resultados são os esperados?
Copiei suas funções no Python 3.8.3 e tentei alguns exemplos por conta própria.
factor_quadratic_equation(1,4,3)retornou '(x + 1) (x + 3)'. Isso é bom.
factor_quadratic_equation(0.5,2,1.5)retornou '0,5 (x + 1) (x + 3)'. Também é bom.
factor_quadratic_equation(0.125,0.5,0.375)retornou '(x + 2) ^ 2 - 1'. O que? Por que a resposta não é '0,125 (x + 1) (x + 3)'? Como '(x + 2) ^ 2 - 1' é considerado o mesmo polinômio que (1/8) x ^ 2 + (1/2) x + (3/8), muito menos sendo considerado uma fatoração desse polinômio?
Eu posso entender que quando um quadrático real não tem zeros e, portanto, literalmente não pode ser fatorado em monômios reais, você pode recorrer à representação do vértice como uma explicação útil, mas esta função parece ansiosa demais para voltar a essa representação para quadráticos com zeros.
factor_quadratic_equation(1.33,1.2,0)retorna
'1.1102230246251565e-15x (1197957500880552x + 1080863910568919)'.
Suponho que isso tenha algo a ver com as representações inexatas de 1,33 e 1,2 no IEEE 754, mas parece bizarro.
factor_quadratic_equation(133,120,0) produziu um traceback, no fundo do qual foi
ValueError: literal inválido para int () com base 10: ''
E ainda factor_quadratic_equation(133/2,120/2,0)retorna '0,5x (133x + 120)', como seria de esperar.
factor_quadratic_equation(6,5,0) também produziu um traceback.
Quais você acha que deveriam ser os resultados em todos esses casos? Ainda tenho algumas dúvidas sobre o estilo de codificação, mas acho que o comportamento correto é uma prioridade ainda maior.