Présentation du problème
On souhaite évaluer une expression arithmétique (ou logique). Pour ce faire, il faut franchir plusieurs étapes qui font l'objet d'un exercice classique d'un cours de théorie des langages que nous n'aborderons ici que très superficiellement et de manière simplifiée.
Analyse lexicale
La première est l'analyse lexicale qui consiste à découper la suite des symboles qui constituent l'expression en unités lexicales ou jetons. Ces jetons doivent respecter des règles syntaxiques, suite de chiffres pour un entier, suite de chiffres contenant éventuellement un point pour les nombres réels, symbole unique \(+,x,-,/\) pour les opérateurs arithmétiques, etc.L'expression \(E\) ci-dessus fournit la liste de jetons suivante (la nature de chaque jeton est indiquée, valeur numérique, opérateur binaire, unaire, parenthèse) :
Analyse syntaxique
L'expression doit respecter une syntaxe, comme les formules de la logique propositionnelle par exemple. Il doit y avoir autant de parenthèses ouvrantes que fermantes, les opérations binaires doivent contenir deux opérandes de part et d'autre de l'opérateur, etc. La description formelle de ces règles syntaxiques se fait à l'aide d'une grammaire décrivant un langage. Le rôle de l'analyse syntaxique est de retracer la suite des règles qui ont permis de construire l'expression en partant d'une règle appelée axiome.
Cette analyse peut être ascendante ou descendante selon que l'on dérive les règles en partant de l'axiome pour aboutir à l'expression, ou que l'on déconstruit l'expression*(*) C'est cette approche que nous utilisons naïvement pour analyser l'expression.. Cette analyse permet de construire l'arbre syntaxique ou arbre de dérivation de l'expression. Chaque opération \(q\)-aire est codée par un arbre \(q\)-aire dont la racine est l'opérateur et les \(q\) fils sont les \(q\) opérandes.
Le procédé est éminemment récursif, si l'opérande est lui même une expression, il est remplacé par son arbre syntaxique jusqu'à obtenir une valeur atomique pour constituer une feuille de cet arbre.
Évaluation de l'expression
Cet arbre est ensuite traité par un algorithme récursif. Si sa racine contient une valeur (dans ce cas, le sous-arbre est une feuille), l'algorithme renvoie cette valeur . S'il s'agit d'un opérateur binaire, l'algorithme s'appelle récursivement sur les deux sous-arbres gauche et droit et une fois les deux valeurs de retour obtenues, il est en mesure d'effectuer l'opération et de renvoyer le résultat. L'évaluation d'une expression arithmétique ou logique en notation infixe où les opérateurs binaires sont placés entre les opérandes est donc un problème qui n'a rien de trivial.Expression postfixe et utilisation d'une pile
Notation postfixe
Une opération binaire, comme l'addition \(x+y\) dans \(\mathbb Z,\) ou la composition \(f\circ g\) de deux applications par exemple, peuvent s'exprimer d'autres manières, celle que nous venons d'employer, la notation infixe, étant la plus commune. On peut également utiliser la notation préfixe dans laquelle l'opérateur est placé avant les opérandes, notation généralement employée pour les fonctions, ici \(+\,(x,y),\) ou encore la notation postfixe \((x,y)\,+\) dans laquelle l'opérateur est placé après les opérandes. Pour un opérateur unaire, les deux notations préfixe et postfixe sont communes, \({\color{lightblue}-}3\) pour l'opposé de \(3\) ou \(M^{\color{olive}t}\) pour la transposée d'une matrice \(M,\) par exemple.La transformation d'une expression infixe en expression postfixe peut être réalisée par l'algorithme de la Voie de triage développé par Dijkstra et qui sera étudié en troisième année. En notant sous forme postfixe \((a,b)\;\diamond\) l'expression infixe \(a\diamond b\) d'un opérateur binaire \(\diamond\) et \((a)\star\) celle d'un opérateur unaire \(\star a,\) l'expression \(E=\;\) fixée plus haut devient :
Les parenthèses ne sont représentées dans l'expression que pour mettre en évidence la logique du passage de la notation infixe à postfixe.
Évaluation à l'aide d'une pile
Plutôt que d'évaluer inductivement l'arbre de dérivation, on utilise une pile pour évaluer l'expression postfixe. L'algorithme est simple, on initialise une pile vide, puis on traite successivement chacun des termes/jetons de l'expression postfixe en lui appliquant l'une des trois règles exclusives suivantes :
La table ci-dessous décrit l'état de la pile (sur fond clair) au fur et à mesure de la lecture des termes de l'expression postfixe précédente. La ligne inférieure contient les termes de l'expression à calculer et le contenu de la pile (après qu'une règle a été appliquée) est écrit au-dessus. Si l'expression syntaxiquement correcte, la pile ne doit contenir que le résultat de l'évaluation (en orange) après le traitement du dernier jeton :
La pile était limitée à \(4\) niveaux*(*) On peut démontrer que c'est suffisant pour évaluer n'importe quelle expression arithmétique ou logique. codés par \(4\) registres \(x,\) \(y,\) \(z\) et \(t,\) du sommet de la pile vers sa base, le registre \(x\) étant celui affiché à l'écran. On dispose d'une touche ENTER qui permet d'empiler un nombre une fois que l'on a fini de le saisir, alors que l'appui sur un opérateur lance le calcul immédiatement et agit sur les registres \(x\) et \(y\) s'il est binaire, sur le registre \(x\) s'il est unaire, cette calculatrice ne dispose donc pas d'une touche \(=.\) La touche \(x ⮂ y\) permet d'échanger les valeurs des deux registres \(x\) et \(y\) au sommet de la pile. Les touches R⭣ et R⭡ font respectivement descendre et monter les valeurs dans la pile.
Outre le mécanisme d'évaluation beaucoup plus simple, cette notation présente un certain nombre d'avantages décisifs :
L'algorithme ÉvaluerPF s'en déduit directement et calque les règles 1 et 2 plus haut. Il utilise deux algorithmes auxiliaires, EstOp qui détermine si le caractère passé en paramètre représente ou non l'un des quatre opérateurs arithmétique, et Opérer qui réalise une opération binaire ou unaire.
ALGORITHME ÉvaluerPF(expr):valeur donnees expr: liste de jetons variables i: entier pile: liste debut i ← 1 pile ← ∅ TQ (i ≤ #expr) FAIRE SI EstOpBin(expr[i]) ALORS y ← Dépiler(pile) x ← Dépiler(pile) z ← Opérer(x,y,expr[i]) Empiler(pile,z) SINONSI EstOpUnr(expr[i]) ALORS x ← Dépiler(pile) z ← Opérer(x,expr[i]) Empiler(pile,z) SINON Empiler(pile,expr[i]) FSI i ← i + 1 FTQ RENVOYER Depiler(pile) FIN
Complexité
L'analyse est immédiate, si l'expression postfixe contient \(n\) termes, le nombre d'instructions à réaliser est en \(\Theta(n).\) En effet, selon la nature du terme courant, ces opérations sont : un empilement (opérande) ; deux dépilements, une opération arithmétique puis un empilement (opérateur). Dans les deux cas le coût est constant \(\Theta(1).\) On suppose ici que les opérations se font sur des nombres de taille bornée, sans quoi il faudrait bien sûr intégrer la complexité de l'opération effectuée fonction de la taille des deux opérandes. On a donc \[T(n)=\Theta(n).\]
Travaux pratiques
Tous les programmes et fonctions sont à écrire en langage C. Une fois compilé, votre programme devra récupérer les arguments de la ligne de commande (autant que de jetons) et renvoyer le résultat de l'évaluation. Par exempleEVALUER 2.25 3 _ −devra renvoyer
5.2500Les paramètres codant en postfixe l'expression infixe \(2.25 - (-3).\)
Déclarez un type TCELL pour coder une cellule d'une liste chaînée contenant un flottant et le pointeur vers la cellule suivante. Déclarez un type TPILE pointant vers un TCELL.