Simplifier une racine carrée en Python peut s’avérer un peu compliqué. C’est pourquoi j’ai créé une classe pour cela.
Simplifier une racine carrée en Python: introduction
J’aime beaucoup la POO et j’ai choisi, pour répondre à la problématique traitée dans cet article, ce paradigme de programmation.
J’ai donc souhaité écrire une classe Python permettant de représenter un radical, c’est-à-dire un nombre de la forme \(a\sqrt{b}\), où a et b sont deux nombres rationnels.
J’ai souhaité implémenter les opérations élémentaires (addition, multiplications) et l’affichage sous la forme la plus réduite possible.
Simplifier une racine carrée en Python: rappels mathématiques
Avant de se lancer dans un tel projet, aussi humble soit-il, il est peut-être nécessaire de faire quelques rappels concernant la simplification de racines carrées.
Prenons un exemple: on souhaite simplifier \(\sqrt{150}\). Pour cela, il faut décomposer en produit de facteurs premiers le radicande (ici, 150):$$150=2\times3\times5^2.$$
Ensuite, on doit extraire les facteurs qui sont élevés à un exposant pair:$$\sqrt{150}=5^{2/2}\sqrt{2\times3}=5\sqrt6.$$
À travers cet exemple, on comprend ce qu’il faut faire.
Vers l’implémentation Python
Pensons que le coefficient et le radicande sont des rationnels; par conséquent, je vais utiliser le module fractions.
Ensuite, il me faut implémenter une fonction qui décompose en produit de facteurs premiers le radicande. Je vais nommer cette fonction decomp(n). Je vais mettre cette fonction à l’extérieur de ma classe. Cette fonction retournera un dictionnaire de la forme { facteurs premiers : exposants }.
Il me faudra ensuite penser aux diverses méthodes de la classe.
Simplifier une racine carrée en Python: la classe
Le constructeur
class Rc: def __init__(self,a,b): self.coef = Fraction(a) self.radicand = Fraction(b)
Ici, rien de fou-fou… 🙂 On déclare les deux arguments comme des rationnels.
Une première méthode pour simplifier la racine carrée
def simp(self): x = self.radicand numer = decomp(x.numerator) denom = decomp(x.denominator) coef_num, radicand_num = 1, 1 for facteur, exposant in numer.items(): coef_num *= facteur**(exposant//2) if exposant%2 == 1: radicand_num *= facteur coef_denom, radicand_denom = 1, 1 for facteur, exposant in denom.items(): coef_denom *= facteur**(exposant//2) if exposant%2 == 1: radicand_denom *= facteur return Fraction(coef_num,coef_denom)*self.coef, Fraction(radicand_num,radicand_denom)
Cette méthode, comme son nom peut le suggérer, consiste à simplifier une racine carrée sans coefficient (donc \(\sqrt{b}\)). La difficulté ici est que ce radicande est considéré comme une fraction. Il faut donc simplifier la racine carrée du numérateur et celle du dénominateur.
La méthode retourne un 2-uplet (coefficient, radicande).
La méthode __str__
Vous le savez déjà sûrement, mais lorsque vous tapez:
>>> print(bidule)
ce qui est affiché est ce que renvoie la méthode __str__ de l’objet bidule.
Ainsi, si je souhaite afficher lisiblement mon objet, je dois formater cet affichage.
def __str__(self): c, r = self.simp()[0], self.simp()[1] if r == 1: return c.__str__() elif c == 1: return f'√({self.simp()[1]})' else: return f'({c})√({r})'
À ce stade de l’implémentation, on a:
>>> a = Rc(1,150)
>>> print( a )
(5)√(6)
L’apparence du résultat est un choix artistique personnel… Libre à vous de l’adapter à votre convenance. Il m’a juste paru plus clair de mettre les nombres entre parenthèses dans le cas où on manipulerait des fractions:
>>> a = Rc(1,Fraction(150,9072))
>>> print( a )
(5/6)√(1/42)
On peut alors bien lire:$$\sqrt{\frac{150}{9072}}=\frac{5}{6}\sqrt{\frac{1}{42}}.$$
Une alternative
Si ce formatage ne vous convient pas, ce que je peux aisément concevoir, et si vous préférez le formatage conventionnel \(k\sqrt{n}\), on peut utiliser la méthode légèrement modifiée:
def __str__(self): c, r = self.simp()[0], self.simp()[1] if r.denominator != 1: c, r = Fraction(c.numerator,c.denominator*r.denominator), Fraction(r.denominator,1) if r == 1: return c.__str__() elif c == 1: return f'√({self.simp()[1]})' else: return f'({c})√({r})'
>>> a = Rc(1,Fraction(150,9072))
>>> print(a)
(5/252)√(42)
Il est vrai que l’on préfère écrire les résultats de sorte qu’il n’y ait pas de radical au dénominateur d’une fraction!
Méthode de multiplication
def __mul__(self, other): c,r = other.coef, other.radicand return Rc( self.coef * c , self.radicand * r)
>>> a, b = Rc(1,150), Rc(1,9072)
>>> print( a*b )
(180)√(42)
Le produit est ici considéré comme une racine carrée, donc comme un objet de la même nature que ceux qui sont multipliés entre eux.
La méthode de soustraction
def __sub__(self, other): return self.__add__(Rc(-other.coef,other.radicand))
Rien de plus simple quand on sait qu’une différence n’est qu’une somme dans le corps des nombres réels…
La méthode de division
def __truediv__(self, other): return self.__mul__( Rc(1/other.coef,1/other.radicand) )
Rien de plus simple quand on sait qu’une division n’est qu’une multiplication dans le corps des nombres réels…
La méthode d’exponentiation
def __pow__(self,n): r = Rc(1,1) for _ in range(n): r *= self return r
Rien de plus simple quand on sait que l’exponentiation est une suite de multiplications 🙂
>>> a = Rc(1,150)
>>> print( a**6 )
3375000
>>> print( a**7 )
(16875000)√(6)
L’implémentation complète
Allez, c’est cadeau mon coco, voici le tout:
from fractions import Fraction def decomp(n): D = dict() # dictionnaire vide k = 2 while n > 1: exposant = 0 while n%k == 0: exposant = exposant + 1 n = n/k if exposant != 0: D[k] = exposant k = k+1 j = 2 while k%j == 0: k = k + 1 return D class Rc: def __init__(self,a,b): self.coef = Fraction(a) self.radicand = Fraction(b) def simp(self): x = self.radicand numer = decomp(x.numerator) denom = decomp(x.denominator) coef_num, radicand_num = 1, 1 for facteur, exposant in numer.items(): coef_num *= facteur**(exposant//2) if exposant%2 == 1: radicand_num *= facteur coef_denom, radicand_denom = 1, 1 for facteur, exposant in denom.items(): coef_denom *= facteur**(exposant//2) if exposant%2 == 1: radicand_denom *= facteur return Fraction(coef_num,coef_denom)*self.coef, Fraction(radicand_num,radicand_denom) def __str__(self): c, r = self.simp()[0], self.simp()[1] if r.denominator != 1: c, r = Fraction(c.numerator,c.denominator*r.denominator), Fraction(r.denominator,1) if r == 1: return c.__str__() elif c == 1: return f'√({self.simp()[1]})' else: return f'({c})√({r})' def __mul__(self, other): c,r = other.coef, other.radicand return Rc( self.coef * c , self.radicand * r)#.__str__() def __add__(self, other): c,r = other.coef, other.radicand s1, s2 = self.simp(), Rc(c,r).simp() if s1[1] == s2[1]: result = Rc(s1[0]+s2[0] , s1[1]) return result.__str__() else: return self.__str__() + ' + ' + other.__str__() def __sub__(self, other): return self.__add__(Rc(-other.coef,other.radicand)) def __truediv__(self, other): return self.__mul__( Rc(1/other.coef,1/other.radicand) ) def __pow__(self,n): r = Rc(1,1) for _ in range(n): r *= self return r