Site de Jean-Michel RICHER

Maître de Conférences en Informatique à l'Université d'Angers

Ce site est en cours de reconstruction certains liens peuvent ne pas fonctionner ou certaines images peuvent ne pas s'afficher.


stacks
counter_1    accueil counter_2    cours counter_3   travaux pratiques

12. TP - Calcul symbolique et Nerdle

12.1. Introduction

Le calcul symbolique (parfois appelé calcul formel) permet de travailler sur des formules mathématiques dotées de variables et de réaliser des calculs sur ses formules sans forcément affecter une valeur aux variables. Les exemples les plus parlant sont, pour une fonction $f(x)$ donnée, de calculer sa dérivée, son intégrale ou la limite de la fonction.

Python propose le package sympy (symbolic mathematics) qui permet de faire du calcul symbolique.

Voici quelques exemples d'utilisation de sympy :

  • calcul de dérivée de $f(x) = x^3 + 2x^2 + 3x + 4$
  • calcul de limite de $f(x) = 1/x$
  • évaluation de la fonction $f(x)=3x-5$ en $1/2$
Afficher le code    ens/l1/python1/tp_nerdle/sympy_ex1.py
  1. """
  2.   Calcul de la dérivée de la fonction
  3.   f(x) = x^3 + 2x^2 + 3x + 4
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir x comme symbole/variable
  11. x = sp.Symbol('x')
  12.  
  13. # Definir la fonction x^3 + 2x^2 + 3x + 4
  14. f = x**3 + 2*x**2 + 3*x + 4
  15.  
  16. # Calculer la derivée ("diff"erentiate) par rapport à x
  17. f_derivee = sp.diff(f, x)
  18.  
  19. # Afficher les résultats
  20. print("Fonction f(x):")
  21. sp.pprint(f)
  22. print("Derivée f'(x):")
  23. sp.pprint(f_derivee)
  24.  
  25.  
Fonction f(x):
 3      2          
x  + 2⋅x  + 3⋅x + 4
Derivée f'(x):
   2          
3⋅x  + 4⋅x + 3
Afficher le code    ens/l1/python1/tp_nerdle/sympy_ex2.py
  1. """
  2.   Calcul de la limite quand x tend vers l'infini
  3.   de f(x) = 1/x
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir la variable et la fonction
  11. x = sp.Symbol('x')
  12. f = 1 / x
  13.  
  14. # Calcul de la limite lorsque x tend vers l'infini sp.oo
  15. limite = sp.limit(f, x, sp.oo)
  16.  
  17. # Afficher les résultats
  18. print("Fonction f(x):")
  19. sp.pprint(f)
  20.  
  21. print("\nLimite de f(x) lorsque x tend vers l'infini:")
  22. sp.pprint(limite)
  23.  
  24.  
Fonction f(x):
1
─
x

Limite de f(x) lorsque x tend vers l'infini:
0
Afficher le code    ens/l1/python1/tp_nerdle/sympy_ex3.py
  1. """
  2.   Evaluation de la fonction f(x) = 3x-5
  3.   pour x=1/2
  4. """
  5. import sympy as sp
  6.  
  7. # Permet l'affichage stylisé avec Unicode
  8. sp.init_printing(use_unicode=True)
  9.  
  10. # Définir la variable et la fonction
  11. x = sp.Symbol('x')
  12. f = 3 * x - 5
  13.  
  14. # Évaluer la fonction pour x = 1/2
  15. # "subs"titute (substituer) x par 1/2
  16. valeur = f.subs(x, 1/2)
  17.  
  18. # Afficher les résultats
  19. print("Fonction f(x):")
  20. sp.pprint(f)
  21.  
  22. print("\nValeur de f(x) pour x = 1/2 :")
  23. sp.pprint(valeur)
  24.  
  25.  
Fonction f(x):
3⋅x - 5

Valeur de f(x) pour x = 1/2 :
-3.50000000000000

12.2. Nerdle

Le jeu Nerdle consiste à résoudre un casse-tête basé sur des nombres.

Nerdle

On dispose de 8 cases à remplir avec des symboles (chiffres, opérateurs et symbol '=') de manière à générer une formule correcte. Par exemple 54/9-4=2.

Le but du jeu consiste à découvrir la formule cachée en 6 coups maximum.

Pour gagner en jouant à Nerdle il suffit de commencer par saisir les formules suivantes :

  • 3*9+0=27
  • 54/6-8=1

En effet, ces deux formules permettent de déterminer :

  • quels chiffres sont utilisés, puisqu'on passe en revue 0, 1, ..., 9
  • quels opérateurs sont utilisés puisqu'on a utilisé +, -, *, /
  • où se trouve l'opérateur d'égalité : en position 6 ou 7, voire en position 5 si les positions 6 et 7 ne sont pas validées

12.3. Travail demandé

12.3.1. Première version

On considère que la formule cachée connue de Nerdle est la formule précédente (54/9-4=2). On imagine que le joueur a pu déterminer :

  • qu'elle est de la forme : x/y-z=2
  • que $x$ est également :
    • soit de la forme 5 , c'est à dire que $x$ est compris entre $50$ et $59$
    • soit de la forme  4, c'est à dire que $x$ est égal à $14$ ou $24$ ou ... ou $94$

Exercice 12.1

On vous demande d'écrire un programme Python nommé assistant_nerdle.py qui va déterminer toutes les variantes pour lesquelles la formule x/y-z=2 est correcte. Il faut s'inspirer des programmes précédents

Pour cela, vous devez fournir en paramètres du programme :

  • la formule de calcul : "x/y-z=2"
  • les intervalles dans lesquels $x$, $y$ et $z$ prennent leurs valeurs, par exemple : "2|1|1"

La notation "2|1|1" signifie que $x$ comporte deux chiffres, que $y$ et $z$ ne comportent qu'un seul chiffre. Cela implique que l'on fera varier $x$ dans l'intervalle $[0-99]$, $y$ et $z$ quant à eux varieront dans l'intervalle $[0-9]$.

Afin d'évaluer la formule, il faudra la transformer pour vérifier qu'elle est égale à 0. Pour cela on modifie la formule initiale en "x/y-z=2" en "x/y-z-(2)" :

expression = sp.sympify(expression.replace("=", "-(") + ")")
  • commnez par récupérer les arguments du programme, stockez le premier paramètre dans la variable expression, le second dans la variable intervalles
  • modifiez expression comme indiqué ci-dessus et transformez intervalles en un tableau (liste) de range()
  • créez une fonction trouver_les_solutions qui prend en paramètres l'expression et les intervalles et qui retourne une liste des solutions trouvées
  • affichez les solutions et indiquez combien il en existe

Testez votre programme avec :

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1"

Vous devriez obtenir 90 solutions :

# ces premières solutions ne sont pas valides car x ne comporte qu'un chiffre 
cela sera corrigé dans la version améliorée
{'x': 2, 'y': 1, 'z': 0}
{'x': 3, 'y': 1, 'z': 1}
{'x': 4, 'y': 1, 'z': 2}
{'x': 4, 'y': 2, 'z': 0}
{'x': 5, 'y': 1, 'z': 3}
{'x': 6, 'y': 1, 'z': 4}
{'x': 6, 'y': 2, 'z': 1}
{'x': 6, 'y': 3, 'z': 0}
{'x': 7, 'y': 1, 'z': 5}
{'x': 8, 'y': 1, 'z': 6}
{'x': 8, 'y': 2, 'z': 2}
{'x': 8, 'y': 4, 'z': 0}
{'x': 9, 'y': 1, 'z': 7}
{'x': 9, 'y': 3, 'z': 1}
{'x': 10, 'y': 1, 'z': 8}
{'x': 10, 'y': 2, 'z': 3}
{'x': 10, 'y': 5, 'z': 0}
{'x': 11, 'y': 1, 'z': 9}
{'x': 12, 'y': 2, 'z': 4}
{'x': 12, 'y': 3, 'z': 2}
{'x': 12, 'y': 4, 'z': 1}
{'x': 12, 'y': 6, 'z': 0}
{'x': 14, 'y': 2, 'z': 5}
{'x': 14, 'y': 7, 'z': 0}
{'x': 15, 'y': 3, 'z': 3}
{'x': 15, 'y': 5, 'z': 1}
{'x': 16, 'y': 2, 'z': 6}
{'x': 16, 'y': 4, 'z': 2}
{'x': 16, 'y': 8, 'z': 0}
{'x': 18, 'y': 2, 'z': 7}
{'x': 18, 'y': 3, 'z': 4}
{'x': 18, 'y': 6, 'z': 1}
{'x': 18, 'y': 9, 'z': 0}
{'x': 20, 'y': 2, 'z': 8}
{'x': 20, 'y': 4, 'z': 3}
{'x': 20, 'y': 5, 'z': 2}
{'x': 21, 'y': 3, 'z': 5}
{'x': 21, 'y': 7, 'z': 1}
{'x': 22, 'y': 2, 'z': 9}
{'x': 24, 'y': 3, 'z': 6}
{'x': 24, 'y': 4, 'z': 4}
{'x': 24, 'y': 6, 'z': 2}
{'x': 24, 'y': 8, 'z': 1}
{'x': 25, 'y': 5, 'z': 3}
{'x': 27, 'y': 3, 'z': 7}
{'x': 27, 'y': 9, 'z': 1}
{'x': 28, 'y': 4, 'z': 5}
{'x': 28, 'y': 7, 'z': 2}
{'x': 30, 'y': 3, 'z': 8}
{'x': 30, 'y': 5, 'z': 4}
{'x': 30, 'y': 6, 'z': 3}
{'x': 32, 'y': 4, 'z': 6}
{'x': 32, 'y': 8, 'z': 2}
{'x': 33, 'y': 3, 'z': 9}
{'x': 35, 'y': 5, 'z': 5}
{'x': 35, 'y': 7, 'z': 3}
{'x': 36, 'y': 4, 'z': 7}
{'x': 36, 'y': 6, 'z': 4}
{'x': 36, 'y': 9, 'z': 2}
{'x': 40, 'y': 4, 'z': 8}
{'x': 40, 'y': 5, 'z': 6}
{'x': 40, 'y': 8, 'z': 3}
{'x': 42, 'y': 6, 'z': 5}
{'x': 42, 'y': 7, 'z': 4}
{'x': 44, 'y': 4, 'z': 9}
{'x': 45, 'y': 5, 'z': 7}
{'x': 45, 'y': 9, 'z': 3}
{'x': 48, 'y': 6, 'z': 6}
{'x': 48, 'y': 8, 'z': 4}
{'x': 49, 'y': 7, 'z': 5}
{'x': 50, 'y': 5, 'z': 8}
{'x': 54, 'y': 6, 'z': 7}
{'x': 54, 'y': 9, 'z': 4}
{'x': 55, 'y': 5, 'z': 9}
{'x': 56, 'y': 7, 'z': 6}
{'x': 56, 'y': 8, 'z': 5}
{'x': 60, 'y': 6, 'z': 8}
{'x': 63, 'y': 7, 'z': 7}
{'x': 63, 'y': 9, 'z': 5}
{'x': 64, 'y': 8, 'z': 6}
{'x': 66, 'y': 6, 'z': 9}
{'x': 70, 'y': 7, 'z': 8}
{'x': 72, 'y': 8, 'z': 7}
{'x': 72, 'y': 9, 'z': 6}
{'x': 77, 'y': 7, 'z': 9}
{'x': 80, 'y': 8, 'z': 8}
{'x': 81, 'y': 9, 'z': 7}
{'x': 88, 'y': 8, 'z': 9}
{'x': 90, 'y': 9, 'z': 8}
{'x': 99, 'y': 9, 'z': 9}
il y a  90  solutions

12.3.2. Version améliorée

Exercice 12.2

Dans un second temps, on modifiera le programme précédent afin d'introduire un troisième paramètre qui indique la forme de $x$, $y$ et $z$, comme par exemple "5.|.|."

Où ici, "5.|.|." indique que $x$ commence par $5$ et est suivi d'un chiffre (compris entre 0 et 9) et que $y$ et $z$ sont composés d'un chiffre quelconque.

Modifiez le programme comme suit :

  • récupérez le troisième paramètre et le stocker dans une variable filtres
  • transformez le contenu de cette variable en une liste [ "5.", ".", "." ]
  • ajoutez le package "re" au début de votre programme qui permet de traiter les expressions régulières
  • après avoir récupéré toutes les solutions après appel de la fonction trouver_les_solutions, appliquez les filtres et ne garder que les solutions qui valides les filtres

Testez votre programme modifié avec les alternatives suivantes, vous devriez obtenir les résultats indiqués :

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "5.|.|."
il y a 90 solution(s)
il y a 6 solution(s) après filtrage
{'x': 50, 'y': 5, 'z': 8}
{'x': 54, 'y': 6, 'z': 7}
{'x': 54, 'y': 9, 'z': 4}
{'x': 55, 'y': 5, 'z': 9}
{'x': 56, 'y': 7, 'z': 6}
{'x': 56, 'y': 8, 'z': 5}


\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "7.|.|."
il y a 90 solution(s)
il y a 4 solution(s) après filtrage
{'x': 70, 'y': 7, 'z': 8}
{'x': 72, 'y': 8, 'z': 7}
{'x': 72, 'y': 9, 'z': 6}
{'x': 77, 'y': 7, 'z': 9}

\$ python3 assistant_nerdle.py "x/y-z=2" "2|1|1" "5.|6|."
il y a 90 solution(s)
il y a 1 solution(s) après filtrage
{'x': 54, 'y': 6, 'z': 7}