Facteur et solveur d'expression quadratique
J'ai décidé de créer un programme capable de factoriser et de résoudre des expressions quadratiques en micropython, où la bibliothèque standard est limitée, et je n'ai aucune idée de comment y implémenter des modules externes, j'ai donc dû créer ce programme à partir de zéro.
Le principe simple est qu'il peut factoriser et résoudre la plupart des expressions quadratiques et afficher les solutions de manière à faciliter la vérification de votre solution.
J'ai documenté une partie du programme pour qu'il soit plus facile de comprendre ce que fait chaque partie du programme. J'aimerais quelques conseils pour optimiser et rendre le programme plus efficace et compact, basé sur l'idée du 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
Réponses
Y a-t-il un endroit factor_quadratic_equationoù la valeur de retour de flip_sign_if_negative(x,sign)est autre chose que la valeur absolue de x? Sinon, je recommanderais d'utiliser la valeur absolue, car c'est déjà une fonction familière.
Pourquoi a/-1plutôt que -a?
Les parties simples sont bien documentées (bien que la plupart d'entre elles soient faciles à comprendre même sans documentation), mais il y a ensuite des parties compliquées avec peu ou pas d'explications. Et je ne suis pas convaincu que vous ayez beaucoup réfléchi à ce que vous voulez vraiment factor_quadratic_equationfaire.
Vous avez écrit ici un algorithme assez compliqué. L'avez-vous testé pour voir si les résultats sont ceux que vous attendiez?
J'ai copié vos fonctions dans Python 3.8.3 et j'ai essayé quelques exemples de ma part.
factor_quadratic_equation(1,4,3)renvoyé '(x + 1) (x + 3)'. C'est bon.
factor_quadratic_equation(0.5,2,1.5)renvoyé '0,5 (x + 1) (x + 3)'. Bien aussi.
factor_quadratic_equation(0.125,0.5,0.375)renvoyé '(x + 2) ^ 2 - 1'. Quoi? Pourquoi la réponse n'est-elle pas «0,125 (x + 1) (x + 3)»? Comment '(x + 2) ^ 2 - 1' est-il même considéré comme le même polynôme que (1/8) x ^ 2 + (1/2) x + (3/8), et encore moins comme une factorisation de ce polynôme?
Je peux comprendre que lorsqu'un quadratique réel n'a pas de zéros et ne peut donc littéralement pas être pris en compte dans de vrais monômes, vous pouvez revenir à la représentation des sommets comme explication utile, mais cette fonction semble trop désireuse de revenir à cette représentation pour les quadratiques avec des zéros.
factor_quadratic_equation(1.33,1.2,0)renvoie
«1.1102230246251565e-15x (1197957500880552x + 1080863910568919)».
Je suppose que cela a quelque chose à voir avec les représentations inexactes de 1.33 et 1.2 dans IEEE 754, mais cela semble bizarre.
factor_quadratic_equation(133,120,0) a produit une trace, au bas de laquelle était
ValueError: littéral invalide pour int () avec base 10: ''
Et factor_quadratic_equation(133/2,120/2,0)renvoie pourtant «0,5x (133x + 120)», comme on pouvait s'y attendre.
factor_quadratic_equation(6,5,0) a également produit un retraçage.
Selon vous, quels devraient être les résultats dans tous ces cas? J'ai encore des questions sur le style de codage, mais je pense qu'un comportement correct est une priorité encore plus élevée.