Série 10 – Diagrammes à tige et à feuilles : corrigé

Introduction

Le code du corrigé est disponible sous la forme d'une archive Zip. Les solutions aux différents exercices sont brièvement discutées ci-dessous.

Exercice 1 : Diagramme unidirectionnel

Le gros du travail consiste à écrire la méthode qui « dessine » effectivement le diagramme, appelée drawPlot ci-dessous. Cette méthode peut ensuite être appelée à la fois depuis le constructeur — pour effectuer l'affichage initial — puis depuis la méthode update — pour mettre à jour l'affichage chaque fois qu'une cellule change de valeur. Bien entendu, afin d'être averti des changements de valeur dans les cellules, il faut s'enregistrer comme observateur de celles-ci, ce qui se fait dans le constructeur.

La méthode drawPlot commence par grouper les valeurs par tige. Pour ce faire, il est naturel d'utiliser une table associative associant à chaque tige la liste de ses feuilles. Aussi bien les tiges que les feuilles sont des entiers, et la table a donc le type Map<Integer, List<Integer>>.

Une fois la table associative construite, il faut déterminer la plage des tiges, c-à-d la plus petite et la plus grande tige trouvées dans les données. Cela se fait facilement au moyen des méthode min et max de Collections appliquées à l'ensemble des clefs de la table.

Finalement, il faut, pour chaque valeur de tige comprise entre le minimum et le maximum, obtenir la liste des feuilles correspondant à cette tige (si elle existe), la trier puis l'afficher au côté de la tige.

Le code final de la méthode drawPlot ressemble donc à ceci :

private void drawPlot() {
    Map<Integer, List<Integer>> stems = new HashMap<>();
    for (Cell c: cells) {
        int stem = c.value() / 10;
        int leaf = c.value() % 10;
        stems.computeIfAbsent(stem, k -> new ArrayList<>())
            .add(leaf);
    }
    int minStem = Collections.min(stems.keySet());
    int maxStem = Collections.max(stems.keySet());
    System.out.println();
    for (int stem = minStem; stem <= maxStem; ++stem) {
        List<Integer> leaves =
            stems.getOrDefault(stem,
                               Collections.emptyList());
        Collections.sort(leaves);
        StringBuilder leavesB = new StringBuilder();
        for (int l: leaves)
            leavesB.append(l);
        System.out.println(String.format("%3d|%s",
                                         stem,
                                         leavesB));
    }
}

Exercice 2 : Digramme bidirectionnel

La classe BiStemPlot ressemble beaucoup à la classe StemPlot mais la plupart des opérations doivent être effectuées à double : une fois pour les données de gauche, une fois pour les données de droite.

Pour éviter de dupliquer trop de code, il convient bien entendu d'introduire de nouvelle méthodes et de les utiliser pour traiter aussi bien les données de gauche que les données de droite. Pour ce faire, le plus simple est probablement de copier la classe StemPlot et d'utiliser la fonction Extract Method… du menu Refactor d'Eclipse.

Comme on l'a vu dans l'exercice 1, la méthode drawPlot se décompose naturellement en trois étapes. La première de ces étapes, qui calcule la table associant une liste de feuilles à chaque tige rencontrée, peut être extraite dans la première méthode privée nommée stemsFor et se présentant ainsi :

Map<Integer, List<Integer>> stemsFor(List<Cell> cells) {
    Map<Integer, List<Integer>> stems = new HashMap<>();
    for (Cell c: cells) {
        int stem = c.value() / 10;
        int leaf = c.value() % 10;
        stems.computeIfAbsent(stem, k -> new ArrayList<>())
            .add(leaf);
    }
    return stems;
}

La seconde étape, qui calcule la plage des tiges, est assez courte pour ne pas nécessiter de méthode auxilliaire. La partie de la troisième étape qui produit une chaîne de caractères correspondant aux feuilles d'une tige donnée peut par contre également être extraite dans une méthode auxilliaire, nommée ici leavesForStem, dont le paramètre booléen reversed dit si l'ordre des feuilles doit être inversé :

String leavesForStem(int stem,
                     Map<Integer, List<Integer>> stems,
                     boolean reversed) {
    StringBuilder leavesB = new StringBuilder();
    stems.getOrDefault(stem, Collections.emptyList())
        .stream()
        .sorted()
        .forEach(leavesB::append);
    return (reversed ? leavesB.reverse() : leavesB)
        .toString();
}

Notez que la programmation par flots a été utilisée ici pour simplifier le code.

Au moyen de ces deux méthodes auxilliaire, la méthode drawPlot de BiStemPlot s'écrit relativement facilement :

private void drawPlot() {
    Map<Integer, List<Integer>> stemsL = stemsFor(cellsLeft);
    Map<Integer, List<Integer>> stemsR = stemsFor(cellsRight);
    int minStem = Math.min(Collections.min(stemsL.keySet()),
                           Collections.min(stemsR.keySet()));
    int maxStem = Math.max(Collections.max(stemsL.keySet()),
                           Collections.max(stemsR.keySet()));
    System.out.println();
    for (int stem = minStem; stem <= maxStem; ++stem) {
        String leavesL = leavesForStem(stem, stemsL, true);
        String leavesR = leavesForStem(stem, stemsR, false);
        System.out.println(String.format("%30s|%1d|%s",
                                         leavesL,
                                         stem,
                                         leavesR));
    }
}

L'utilisation d'une largeur de 30 caractères pour les feuilles de gauche n'est pas propre mais simplifie les choses…