TP — Séance 1 — ADT et interfaces Java
Écrire, tester et mesurer un deque sur tableau circulaire en Java
Écrire le deque du cours sur un tableau circulaire redimensionnable (ArrayDeque<T>, contrat Deque<T>), le faire passer par les tests JUnit fournis, s’en servir comme pile pour évaluer des expressions postfixes et vérifier des parenthésages, puis mesurer le coût amorti du doublement et le coût caché d’une file sur ArrayList. Durée : une séance de TP.
À rendre. Le dépôt Git du projet (les classes complétées, les tests au vert) et un compte rendu d’une à deux pages qui répond aux questions marquées Observer, Expliquer et Modifier, en recopiant les sorties obtenues.
Préparation
Le TP se fait dans l’environnement Docker du cours (Java 17, Gradle 8, JUnit 5). Sur la machine, décompresser le code de départ, TP_S01_code_depart.zip, dans ~/dsa : le dossier TP_S01_code_depart/ apparaît. Le conteneur se lance depuis ce dossier :
cd ~/dsa
docker run -it --rm -v "$(pwd):/workspace" elbachirtoure/dsa-java:v2.0Sous Windows (Git Bash), la commande docker run est précédée de MSYS_NO_PATHCONV=1. Dans le conteneur :
cd /workspace
new-project tp-s01
cd tp-s01
cp -r ../TP_S01_code_depart/. .
rm src/test/java/MainTest.javaLe code de départ contient six fichiers, cinq dans src/main/java/ et DequeTest.java dans src/test/java/ : la copie les range dans le projet, en remplaçant le Main.java créé par new-project, et rm supprime le MainTest.java créé par new-project. Le dépôt Git du projet se crée dans tp-s01 : git init -b main et l’identité locale, comme à l’étape 7 du TP-1.
| Fichier | Rôle |
|---|---|
Deque.java |
le contrat : addFirst, addLast, removeFirst, removeLast, peekFirst, peekLast, size, isEmpty ; fourni |
ArrayDeque.java |
les champs donnees, tete, taille, les compteurs copies et agrandissements, size, isEmpty, capacite, indiceTete, etat fournis ; suivant, precedent (étape 1), les six opérations d’extrémité (étape 2) et agrandir (étape 3) à écrire |
Postfixe.java |
evaluer à écrire à l’étape 5 ; estOperateur, appliquer fournis |
Parentheses.java |
estEquilibree à écrire à l’étape 5 ; correspond fourni |
Main.java |
programmes d’observation, fourni |
DequeTest.java |
quatorze tests JUnit, fournis |
Les méthodes à écrire contiennent une consigne en commentaire et lèvent UnsupportedOperationException. Les deux commandes du TP sont gradle --no-daemon test et gradle --no-daemon run --args="mode", où mode vaut observer, calculs ou mesures. Le nom ArrayDeque est celui de la classe du projet : aucun fichier n’importe java.util.ArrayDeque.
Observer. Lancer les tests avant d’écrire la moindre ligne : combien échouent, et avec quelle exception ?
Étape 1 : les indices de l’anneau
Écrire suivant(i) et precedent(i) de ArrayDeque : l’indice qui suit et celui qui précède la case \(i\) sur un anneau de donnees.length cases.
- Expliquer. Que vaut
(0 - 1) % 6en Java ? Pourquoiprecedentajoute-t-ildonnees.lengthavant de prendre le reste ? Pour quelles valeurs de \(i\) ce terme change-t-il le résultat ? - Expliquer.
suivantpourrait-il s’écrirei + 1 == donnees.length ? 0 : i + 1? Comparer avec le code dejava.util.ArrayDeque(méthodeinc).
Étape 2 : les six opérations d’extrémité
Écrire addFirst, addLast, removeFirst, removeLast, peekFirst et peekLast. Les ajouts appellent agrandir() quand le tableau est plein ; elle sera écrite à l’étape 3.
- Observer. Les tests
ordreFileEtPile,reboucleParLaFin,reboucleParLaTete,peekNeModifiePasetvideLeveUneExceptiondoivent passer. - Expliquer. Quelle expression donne l’indice du dernier élément ? Dérouler le test
reboucleParLaFin: les cases occupées après chaque boucle, et la case du dernier élément. - Expliquer. Pourquoi
removeFirstécrit-ilnulldans la case qu’il libère, alors que le contrat ne l’exige pas ? - Modifier. Remplacer provisoirement le corps de
precedentparreturn (i - 1) % donnees.length;. Quels tests échouent, avec quel message ? Que fait le modeobserver? Revenir au code correct.
Étape 3 : agrandir dans l’ordre logique
Écrire agrandir() : un tableau deux fois plus grand, où l’élément de rang \(k\) (case \((\texttt{tete}+k) \bmod m\)) est recopié en case \(k\), avec copies++ pour chaque case recopiée ; puis tete redevient 0 et agrandissements augmente.
- Observer. Les neuf tests qui ne portent ni sur
Postfixeni surParenthesesdoivent passer. - Expliquer. Retrouver par le calcul les 8 agrandissements et les 2 040 copies attendus par
copiesDuDoublement. Comparer à \(2n\). - Expliquer. Dans
agrandirApresRebouclage, donner les cases occupées et la valeur detetejuste avantaddLast(11). - Modifier. Remplacer provisoirement la boucle de recopie par
System.arraycopy(donnees, 0, neuf, 0, taille);(etcopies += taille;). Quels tests échouent, avec quelles valeurs ? Expliquer chacune. Revenir au code correct.
Étape 4 : observer les indices
Le mode observer crée un deque de 6 cases et affiche, après chaque opération, tete, la fin \((\texttt{tete}+\texttt{taille}) \bmod m\), taille, la capacité et les cases (. : case vide) : des clients arrivent à un guichet de transfert d’argent des Parcelles Assainies, par la fin ou, prioritaires, par le début.
- Observer. Lancer
gradle --no-daemon run --args="observer"et recopier la sortie. - Expliquer. Après le premier
addFirst, que vauttete, et pourquoi ? Relever une ligne où la fin est inférieure àtete: que signifie-t-elle ? - Expliquer. À quelle opération la capacité change-t-elle ? Que vaut
tetejuste après, et dans quel ordre les clients sont-ils rangés ? - Modifier. Ajouter à
DequeTestdeux tests : l’un alterneaddFirstetaddLastsur un deque vide puis vérifie l’ordre complet de sortie ; l’autre fait boucler la tête paraddFirst, puis agrandit le tableau. Lesquels échouent avec la modification de l’étape 2 (d) ?
Étape 5 : une pile par restriction
Un deque restreint à addLast et removeLast est une pile : Postfixe et Parentheses n’utilisent que ces deux opérations. Écrire evaluer et estEquilibree en suivant les consignes.
- Observer. Les quatorze tests doivent passer. Lancer le mode
calculset recopier sa sortie. - Expliquer. Dérouler à la main
2000 350 4 * -: la pile après chaque jeton. Que représente ce calcul, en FCFA, pour un billet de 2 000 et 4 articles à 350 ? - Expliquer. Pour chacune des trois expressions rejetées du mode
calculs, dire quelle instruction rendfalse. - Modifier. Échanger provisoirement les deux
removeLast()deevaluer. Quels tests échouent ? Quelles lignes du modecalculschangent, et pourquoi la deuxième ne change-t-elle pas ? Revenir au code correct. - Modifier. Remplacer provisoirement le
return pile.isEmpty();final deestEquilibreeparreturn true;. Quel test échoue, sur quelle expression ? Revenir au code correct.
Étape 6 : les coûts mesurés
Le mode mesures fait jusqu’à \(131\,073\) ajouts en fin dans un ArrayDeque de 8 cases et écrit, à six repères, les agrandissements, les copies, les copies par ajout et le plus grand nombre de copies d’un seul ajout (pire). Il sert ensuite \(n\) clients de deux façons, avec une java.util.ArrayList (add, puis remove(0)) et avec l’ArrayDeque du projet (addLast, puis removeFirst), et écrit les durées en millisecondes.
- Observer. Lancer le mode
mesureset recopier les deux tableaux. - Expliquer. Pourquoi le nombre de copies par ajout vaut-il presque 1 pour \(131\,072\) ajouts et presque 2 pour \(131\,073\) ? Montrer que les copies valent au plus \(2n-10\) pour \(n>8\) avec une capacité initiale de 8.
- Modifier. Remplacer provisoirement
2 * donnees.lengthpardonnees.length + 16dansagrandir. Quels tests échouent ? Relancer le modemesures: comment les copies évoluent-elles quand \(n\) est multiplié par 50, puis par 1,31 ? Comparer à \(n^2/32\). Revenir au code correct. - Expliquer. Dans le second tableau, comment évoluent les durées quand \(n\) double, pour chaque réalisation ? Relier
remove(0)au décalage de la liste sur tableau du cours. - Expliquer. Relancer deux fois le mode
mesures: quelles colonnes changent, et pourquoi ?
Pour cette séance
Le cours
Les slides
À lire en ligne
À télécharger
- Code de départ du TP (ZIP, 6 Ko)