Série 5 – Listes : corrigé
Introduction
Le code du corrigé est disponible sous la forme d'une archive Zip, qui contient également le code de l'énoncé. Les solutions aux différents exercices sont brièvement discutées ci-dessous.
Exercice 1 – tableaux-listes
Méthode set
La méthode set
est triviale à définir pour les tableaux-listes puisqu'il suffit, après avoir vérifié la validité de l'index, de modifier l'entrée correspondante du tableau sous-jacent.
public void set(int i, E newElem) { checkIndex(i); array[i] = newElem; }
Méthode toString
La principale difficulté de la méthode toString
est d'éviter de placer une virgule après le dernier élément. Pour ce faire, on peut simplement utiliser la méthode hasNext
de l'itérateur à l'intérieur de la boucle de parcours des éléments, après avoir obtenu le prochain élément au moyen de next
.
Pour construire la chaîne de caractère, on utilise — comme le suggère l'énoncé — le bâtisseur de chaîne fourni par Java, StringBuilder
. Ce bâtisseur joue exactement le même rôle que les bâtisseurs du projet, c-à-d qu'il permet de construire petit-à-petit une instance d'une classe immuable, ici String
. A noter que la méthode que nous appelons habituellement build
dans le projet est simplement toString
pour ce bâtisseur.
@Override public String toString() { StringBuilder b = new StringBuilder("["); Iterator<E> it = iterator(); while (it.hasNext()) { b.append(it.next()); if (it.hasNext()) b.append(","); } return b.append("]").toString(); }
Cette méthode toString
n'utilise qu'une seule méthode de la classe qui la contient, à savoir iterator
. Dès lors, elle est utilisable pour n'importe quelle liste, et même n'importe quelle classe implémentant l'interface Iterable
.
Exceptions de la méthode remove
La manière la plus simple de savoir s'il est autorisé d'appeler la méthode remove
consiste — comme suggéré dans l'énoncé — à ajouter un champ booléen nommé canRemove
à l'itérateur. Ce champ est vrai si et seulement si il est autorisé d'appeler remove
.
private static final class ALIterator<E> implements Iterator<E> { private final ArrayList<E> list; private int index; private boolean canRemove; public ALIterator(ArrayList<E> list) { this.list = list; this.index = 0; this.canRemove = false; } @Override public boolean hasNext() { return index < list.size(); } @Override public E next() { if (! hasNext()) throw new NoSuchElementException(); canRemove = true; return list.get(index++); } @Override public void remove() { if (! canRemove) throw new IllegalStateException(); list.remove(index - 1); canRemove = false; } }
Exercice 2 – listes chaînées
Méthode set
La méthode set
des listes chaînées n'est pas beaucoup plus difficile à définir que celle des tableaux-listes, étant donné que la méthode nodeAtIndex
permet d'obtenir un nœud à partir de son index.
public void set(int i, E newElem) { checkIndex(i); nodeAtIndex(i).value = newElem; }
Itérateur
L'itérateur sur les listes chaînées ressemble à celui sur les tableaux-listes, si ce n'est qu'au lieu de représenter la position de l'itération au moyen d'un index, il utilise un nœud.
Au même titre que l'itérateur sur les tableaux-listes initialisait son index à 0 pour référencer le premier élément de la liste, l'itérateur sur les listes chaînées référence initialement le successeur du nœud d'en-tête. En effet, ce nœud est le premier à contenir une information utile.
Pour passer à l'élément suivant, cet itérateur se contente de suivre le lien du nœud courant vers son successeur.
Pour détecter la fin de l'itération, cet itérateur compare le nœud courant avec le nœud d'en-tête, et s'arrête (dans le sens où sa méthode hasNext
retourne faux) dès que ce dernier est rencontré.
Finalement, pour savoir s'il est valide d'appeler remove
, cet itérateur utilise exactement la même technique que l'itérateur sur les tableaux-listes. Pour effectuer la suppression, la méthode remove
de l'itérateur appelle simplement la méthode privée removeNode
de la classe LinkedList
, qui est également utilisée par la méthode remove
de cette classe-là.
private static final class LLIterator<E> implements Iterator<E> { private final LinkedList<E> list; private Node<E> next; private boolean canRemove; public LLIterator(LinkedList<E> list) { this.list = list; this.next = list.head.succ; this.canRemove = false; } @Override public boolean hasNext() { return next != list.head; } @Override public E next() { if (! hasNext()) throw new NoSuchElementException(); canRemove = true; E value = next.value; next = next.succ; return value; } @Override public void remove() { if (! canRemove) throw new IllegalStateException(); list.removeNode(next.pred); canRemove = false; } }
Méthode toString
La méthode toString
est exactement la même que celle pour les tableaux-listes. Idéalement, elle devrait d'ailleurs être placée dans une super-classe commune du même genre que la classe AbstractList
de la bibliothèque Java.
Exercice 3 – listes itérables
Pour rendre les listes itérables, il suffit de faire en sorte que l'interface List
étende l'interface Iterable
et le tour est joué. On modifie donc cette interface ainsi :
public interface List<E> extends Iterable<E> { // ... comme avant }