====== Arbre de calcul ======
Lorsque l'on écrit un calcul comme $7 \times 6 + 4 \times (15 + 2)$, l'exécution des différents opérateurs se fait dans un ordre bien déterminé qui n'est pas a priori évident.
===== Arbre représentant l'expression =====
On peut créer un arbre dont les nœuds sont les nombres ou les opérateurs.
{{ :nsi:tds:arbre_calcul.png?direct&200 |}}
Notre but est de produire cet arbre.
===== Découpage avec une expression régulière =====
On souhaite découper l'expression de départ en ses constituants de base.
''%%"7 * 6 + 4 *(15+2)"%%'' -> ''%%[7, '*', 6, '+', 4, '*', '(', 15, '+', 2, ')']%%''.
Ce travail est très rapide si on connaît les expressions régulières, ce qui (normalement) n'est pas votre cas. Je vous présente donc un exemple.
**Exemple**
Supposons que je veuille reconnaître les morceaux de l'écriture d'une formule chimique. L'écriture sera de la forme, par exemple H2O ou H3O+ ou CuB7C15Na3 ou encore -- formule qui n'existe pas -- ABe4K8. Imaginons que l'on puisse aussi écrire //molécule:432// et qu'il faille pouvoir le reconnaître.
Pour le premier cas, il s'agit de découper en morceaux qui :
* commencent par exactement une lettre majuscule : ''%%[A-Z]%%''
* suivis de lettres en minuscules : ''%%[a-z]*%%'' -- ''*'' signifie de 0 à plusieurs
* suivis de chiffres : ''%%[0-9]*%%''
* éventuellement suivi de ''+'' : ''%%\+?%%'' -- ''?'' signifie 0 ou 1 fois, comme ''+'' a un usage spécial dans les expressions régulières; ''%%\+%%'' permet de signaler que l'on veut ''+'' en tant que caractère.
Ce qui donne ''[A-Z][a-z]*[0-9]*\+?''
Pour le second cas, il s'agit de reconnaître :
* l'apparition du mot "molécule:" : ''%%molécule:%%''
* suivi d'un nombre : ''%%[0-9]+%%'' -- ''+'' signifie au moins 1 fois.
Le second cas donne ''molécule:[0-9]+''
Puisque c'est soit l'un, soit l'autre, on ajoute entre les deux un symbole de OU : ''|''.
Et voilà notre expression régulière complète : ''%%r"([A-Z][a-z]*[0-9]*\+?)|(molécule:[0-9]+)"%%''
>>> import re # module d'expression régulière
>>> regex = r"([A-Z][a-z]*[0-9]*\+?)|(molécule:[0-9]+)"
>>> re.findall(regex, "CuB7C15Na3")
[('Cu', ''), ('B7', ''), ('C15', ''), ('Na3', '')]
>>> re.findall(regex, "molécule:723")
[('', 'molécule:723')]
Il est possible de faire un peu mieux en faisant bonne usage des parenthèses :
>>> import re # module d'expression régulière
>>> regex = r"(([A-Z][a-z]*)([0-9]*)\+?)|(molécule:([0-9]+))"
>>> re.findall(regex, "CuB7C15Na3")
[('Cu', 'Cu', '', '', ''), ('B7', 'B', '7', '', ''), ('C15', 'C', '15', '', ''), ('Na3', 'Na', '3', '', '')]
>>> re.findall(regex, "molécule:723")
[('', '', '', 'molécule:723', '723')]
Pour chaque tuple réponse, on trouve un item correspondant à une paire de parenthèse. Le découpage est ensuite très simple.
===== Produire l'arbre =====
Je vous propose une méthode pour produire l'arbre à partir de la liste représentant l'expression. Cette méthode n'est pas très performante. Elle a l'avantage d'être simple à comprendre. Je vous en donnerai une autre plus loin, plus efficace mais moins simple.
==== Construction par ordre de priorité ====
Partons du découpage : ''%%[7, '*', 6, '+', 4, '*', '(', 15, '+', 2, ')']%%''.
=== Transformation sous une autre forme ===
Un premier traitement consisterait à parcourir cette liste et à en transformer chaque élément en un nœud d'arbre. Je vais le noter ainsi : ''%%[<7>, <*>, <6>, <+>, <4>, <*>, <(>, <15>, <+>, <2>, <)>]%%''
=== Prise en compte des parenthèses ===
La présence de parenthèses indiquent un niveau de priorité augmenté. Les parenthèse ne servent qu'à cela. On pourrait donc transformer notre tableau sous cette forme ''%%[<7>, <*>, <6>, <+>, <4>, <*>, <15>, <+>+, <2>]%%'', les ''+'' représentent le surcroît de priorité accordé par les ''( )''. Les parenthèses ayant été pris en compte, on a pu les supprimer.
Notez que seuls les opérateurs ont une priorité, les nombres n'ont pas à être priorisés.
=== Sélection de l'opérateur de priorité maximale ===
* L'opérateur de priorité maximale, tenant compte du surcroît accordé par les parenthèses, est le ''<+>+'' en avant dernière position.
* Puisqu'il est prioritaire, on l'exécute en premier en le faisant agir sur ses deux voisins.
* On obtient donc un nœud que l'on pourrait écrire ''<+, <15>, <2>>''.
* Le tableau devient : ''%%[<7>, <*>, <6>, <+>, <4>, <*>, <+, <15>, <2>>]%%''
Notez que maintenant ''<+, 15, 2>'' est un simple nombre. Ce n'est plus un opérateur.
=== Répétition... ===
Si on répète l'opération on obtient :
* sélection du premier ''<*>'' : ''%%[<*, <7>, <6>>, <+>, <4>, <*>, <+, <15>, <2>>]%%''
* sélection du second ''<*>'' : ''%%[<*, <7>, <6>>, <+>, <*, <4>, <+, <15>, <2>>>]%%''
* sélection du ''<+>'' : ''%%[<+, <*, <7>, <6>>, <*, <4>, <+, <15>, <2>>>>]%%''
=== Fin de la boucle ===
Quand il ne reste plus qu'un élément dans la liste, c'est que la construction de l'arbre est terminée. Il suffit de récupérer le dernier élément de la liste.
Dans l'exemple : ''%%<+, <*, <7>, <6>>, <*, <4>, <+, <15>, <2>>>>%%''
===== Évaluer l'expression =====
On souhaite pouvoir évaluer une expression transformée en arbre.
**Exemple :** évaluation de ''%%<+, <*, <7>, <6>>, <*, <4>, <+, <15>, <2>>>>%%''.
* Le nœud racine est un ''+''.
* Il faut évaluer le sous arbre gauche : ''%%<*, <7>, <6>>%%''
* évaluer le sous arbre droite : ''%%<*, <4>, <+, <15>, <2>>>%%''
* faire la somme.
Une approche récursive semble convenir parfaitement.
==== Test ====
Vérifiez que l'évaluation de :
* "7 * 6 + 4 *(15+2)" renvoie bien 110
* "(12 - 6) * 3 - 4 + 2 *(2 + 3 *(5 - 1)*6 - 8) - 2 * 11" renvoie bien 124
===== Aller plus loin =====
* Ajouter des opérateurs
* Permettre des calculs sur les flottants
* Reconnaître des constantes comme $\pi$
* Autoriser l'écriture d'un symbole comme $x$ qu'il faudrait fournir au moment de l'évaluation.
* Prendre en charge correctement d'éventuelles erreurs d'expression