Solutions du TD-3, piles

1  Calculette polonaise

Voici les classes IntList, IntStack et HP.

Je vois peu à dire en plus du code...Il peut être intéressant d'aller voir du côté du TD-5 de l'année dernière et de sa solution. Le problème étant alors un peu différent (le programme de la calculette était donné par le premier argument de la ligne de commande).

2  Calculette américaine bas de gamme

Voici les classes StringList, StringStack et BasGamme.

Là encore, le code parle de lui même...On peut noter l'écriture de reduce avec une boucle ``infinie'', dont ont sort par return.
 static void reduce() {
    while (true) { // boucle sans fin
      String s = stackm.pop () ;    
      if (s.equals("+")) { // Addition, les autres cas sont pareils
        int i1 = stacki.pop ();
        int i2 = stacki.pop ();
        stacki.push (i2 + i1) ;
         .
         .
         .

      } else if (s.equals("[")) {
        return ;
      } else 
        stacki.push (Integer.parseInt(s));
    }
  }
On peu aussi remarquer le source particulièrement court de main (sans les affichages).
  public static void main (String [] arg) {
    for (int i = 0 ; i < arg.length ; i++) {
      String s = arg[i] ;
      if (s.equals("]"))
        reduce () ;
      else
        shift(s) ;
    }
  }
}
Un peu de culture tout de même. L'opération de retrouver une certaine structure dans une écriture linéaire est dite analyse syntaxique (parsing). Typiquement et dans le cas qui nous intéresse, toute ambiguité d'écriture est levée si on considère non plus des écritures du style ``1 + 2 x 3'' mais des arbres :
   +                    x
  / \                  / \
 1   x     ou         +   3
    / \              / \
   2   3            1   2
L'opération de parsing consiste à produire un arbre à partir de l'écriture linéaire. Cette opération, réalisée par un programme qui s'appelle analyseur syntaxique (parser), lève toute espèce d'ambiguité. Certes, dans notre cas on ne produisait pas d'arbre, on calculait à la place. Toutefois la structure d'arbre aide fortement à la compréhension de ce qui se passe : à tout instant la pile stacki contient le résultat de l'évaluation des sous-arbres analysés jusque là, tandis que la pile stackm contient les mots non encore exploités. Les sous-arbres (ou plutôt leur valeur) ont étés empilés lors d'une l'exploitation des mots et sous-arbres qui les fondent. (par ex. dans E1 + E2 donne l'arbre E, l'arbre E est créé en mangeant deux sous arbres et un mot.)

Le parenthésage complet expose la structure d'arbre sous-jacente sous une forme facilement exploitable à l'aide d'une pile. Chaque ouvrante délimitant un début d'un sous-arbre et chaque fermante une fin. Il suffira donc d'empiler les ouvrantes et de se débrouiller lorsque l'on rencontre une fermante pour retrouver et dépiler l'ouvrante correspondante, qui se trouve être la première sur la pile. Le parenthésage abusif a pour unique but d'identifier chaque sous-arbre à créer. Les parenthèses supplémentaires (genre [ [ 1 ] ]) ne servent à rien, mais ne font pas planter le programme.

Un calcul remplace ici une création de sous-arbre. (Cas de entier, création d'une feuille, et des opérateurs création de noeud iterne).

Mais la procédure la plus générale consiste à d'abord faire l'analyse syntaxique, puis toute espèce de travail utile. C'est intelligent, car :

3  Calculette américaine

Voici le source Texas. Par rapport à la précédente méthode main, le source a pris un peu de poids :
  public static void main (String [] arg) {
    shift("[") ; shift("[") ; shift ("[") ;
    for (int i = 0 ; i < arg.length ; i++) {
      String s = arg[i] ;
      if (s.equals("]")) {
        reduce () ; reduce () ; reduce () ;
        shift("[") ; shift("[") ;
      } else if (s.equals("+") || s.equals("-")) {
        reduce () ; reduce () ;
        shift(s) ;
        shift ("[") ; shift ("[") ;
      } else if (s.equals("x") || s.equals("/")) {
        reduce () ;
        shift(s) ;
        shift ("[") ;
      } else
        shift(s) ;
      System.out.println(s + " : " + stacki + "    " + stackm) ;
    }
    reduce () ; reduce () ; reduce () ;
  }
Vous remarquerez qu'il découle directement de l'énoncé. L'énoncé lui même repose sur l'astuce que l'on peut donner les priorités des opérateurs en ajoutant localement quelques empilages et dépilages. Notez que les parenthèses sont ici des sortes d'opérateurs.

Cette façon d'analyser les expressions infixes n'est pas standard... Il y a plus efficace. On peut en particulier définir des relations de précédence entre opérateurs et les utiliser pour savoir quand faire shift et jusqu'où faire reduce. C'est moins immédiat qu'il n'y paraît (voir une autre calculette TexasBis).

Une autre technique d'analyse utlisable ici est l'analyse LL (voir lea références ci-dessus). L'idée est que l'analyseur est un programme récursif qui reproduit la structure de la grammaire des expressions analysées. Le source LL réalise cette analyse.

4  Traduction

Voici le source Traduc.

À la lumière des remarques précédentes sur la dialectique entrée linéaire/arbre, on comprendra que l'on a imprimé un arbre sous forme postfixe, sans construire cet arbre : si il y a lieu, au moment de la fermeture d'une parenthèse de construire un noeud, il est largement temps d'afficher le symbole contenu dans ce noeud.

Pour ceux qui ne le savent pas, imprimer un arbre sous forme postfixe. consiste à parcourir l'arbre en affichant les symboles des noeuds dans l'ordre : afficher le fils gauche, le fils droit, afficher le symbole du noeud.

5  Il reste du temps

Deux classes Stack et NewHP.
Ce document a été traduit de LATEX par HEVEA.