Fattore di espressione quadratico e risolutore
Ho deciso di creare un programma in grado di fattorizzare e risolvere espressioni quadratiche in micropython, dove la libreria standard è limitata, e non ho idea di come implementare moduli esterni su di essa, quindi ho dovuto creare questo programma da zero.
La semplice premessa è che può fattorizzare e risolvere la maggior parte delle espressioni quadratiche e visualizzare le soluzioni in modo da rendere più semplice il controllo della soluzione.
Ho documentato parte del programma per rendere più facile capire cosa fa ogni parte del programma. Vorrei qualche consiglio per ottimizzare e rendere il programma più efficiente e compatto, basato sull'idea di 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
Risposte
C'è un posto in factor_quadratic_equation
cui il valore di ritorno di flip_sign_if_negative(x,sign)
è qualcosa di diverso dal valore assoluto di x
? In caso contrario, consiglierei di utilizzare il valore assoluto, poiché è già una funzione familiare.
Perché a/-1
piuttosto che -a
?
Le parti semplici sono ben documentate (sebbene la maggior parte di esse sarebbe facile da capire anche senza documentazione), ma poi ci sono parti complicate con poche o nessuna spiegazione. E non sono convinto che tu abbia pensato molto a quello che vuoi veramente factor_quadratic_equation
fare.
Hai scritto un algoritmo abbastanza complicato qui. L'hai testato per vedere se i risultati sono quelli che ti aspettavi?
Ho copiato le tue funzioni in Python 3.8.3 e ho provato alcuni miei esempi.
factor_quadratic_equation(1,4,3)
ha restituito '(x + 1) (x + 3)'. Quello è buono.
factor_quadratic_equation(0.5,2,1.5)
ha restituito '0,5 (x + 1) (x + 3)'. Anche buono.
factor_quadratic_equation(0.125,0.5,0.375)
ha restituito '(x + 2) ^ 2 - 1'. Che cosa? Perché la risposta non è "0,125 (x + 1) (x + 3)"? In che modo '(x + 2) ^ 2 - 1' è considerato lo stesso polinomio di (1/8) x ^ 2 + (1/2) x + (3/8), per non parlare di essere considerato una fattorizzazione di quel polinomio?
Posso capire che quando un vero quadratico non ha zeri, e quindi letteralmente non può essere scomposto in veri monomi, potresti ricorrere alla rappresentazione del vertice come spiegazione utile, ma questa funzione sembra fin troppo desiderosa di ricadere su quella rappresentazione per quadratiche con zeri.
factor_quadratic_equation(1.33,1.2,0)
restituisce
"1.1102230246251565e-15x (1197957500880552x + 1080863910568919)".
Suppongo che questo abbia qualcosa a che fare con le rappresentazioni inesatte di 1.33 e 1.2 in IEEE 754, ma sembra bizzarro.
factor_quadratic_equation(133,120,0)
ha prodotto un traceback, in fondo al quale era
ValueError: valore letterale non valido per int () con base 10: ''
E tuttavia factor_quadratic_equation(133/2,120/2,0)
restituisce "0,5x (133x + 120)", come ci si potrebbe aspettare.
factor_quadratic_equation(6,5,0)
ha anche prodotto un traceback.
Quali pensi dovrebbero essere i risultati in tutti questi casi? Ho ancora alcune domande sullo stile di codifica, ma penso che un comportamento corretto sia una priorità ancora più alta.