:: Enseignements :: ESIPE :: E4INFO :: 2025-2026 :: Java Avancé ::
![[LOGO]](http://monge.univ-eiffel.fr/ens/resources/mlv.png) | Trop Graph |
Le but de ce TD est d'implanter une représentation des graphes orientés
sous forme de tableau d'adjacence. Et aussi de voir comment
les types paramétrés, les itérateurs et les streams peuvent être combinés.
Le TD a pour but de fournir une implantation de l'interface
Graph.java. Il s'agit des graphes orientés qui ont un nombre de nœuds fixe
(numérotés de 0 à nodeCount - 1).
L'interface est paramétrée par le type des valeurs des arcs.
Les nœuds ne contiennent pas d'information.
Exercice 1 - Maven
Nous allons utiliser Maven avec la configuration, le
pom.xml, suivante
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fr.uge.graph</groupId>
<artifactId>sed</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.13.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version>
<configuration>
<release>25</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.3</version>
</plugin>
</plugins>
</build>
</project>
Créer un projet Maven (pas un projet Java) puis cocher
create simple project au niveau du premier écran,
puis passer à l'écran suivant en indiquant
Next.
Pour ce TP, le groupId est
fr.uge.graph , l'artefactId est
graph et
la version est
0.0.1-SNAPSHOT. Pour finir, cliquer sur
Finish.
Exercice 2 - Graph
Notre but va être d'écrire une classe
MatrixGraph,
dans le package
fr.uge.graph, une implantation par
tableau d'adjacence de l'interface
Graph.
La structure de données est conceptuellement matrice
nodeCount * nodeCount
telle que l'on stocke le poids d'un arc
(src, dst) dans la case (src, dst).
En fait, habituellement, on ne représente pas une matrice sous forme d'un tableau
à double entrée, car cela veut dire effectuer deux indirections pour trouver
la valeur, ce qui est lent. On alloue un tableau à une seule dimension de taille
nodeCount * nodeCount et on se balade dedans en faisant des additions
et des multiplications.
Les tests unitaires qui vérifient que votre implantation est bien conforme sont là :
GraphTest.java
-
On souhaite écrire la classe MatrixGraph qui est seule implantation possible de
l'interface Graph.
L'interface Graph est définie par le fichier
Graph.java.
Vous pouvez noter que l'interface Graph est correctement documentée
en utilisant le format javadoc. Non seulement on indique pour chaque méthode publique
ce que fait la méthode, à quoi correspond chaque paramètre, la valeur de retour attendue mais
on documente aussi les exceptions susceptibles d'être levées.
Dans un premier temps, on va seulement implanter
la méthode nodeCount(). Pour l'instant, mettez en commentaires les autres méthodes de l'interface Graph.
On souhaite que la classe MatrixGraph, qui est une classe d'implantation,
ne soit pas visible de l'extérieur.
Comme il faut une façon de créer une instance de MatrixGraph
pour l'utilisateur, nous allons déclarer dans l'interface Graph
une factory method of(nodeDount) qui renvoie
une instance de MatrixGraph. Quelle méthode doit faire les vérifications sur nodeCount ? Le constructeur ou la factory ?
Écrire la classe MatrixGraph, ses champs, son constructeur et
la méthode nodeCount.
Vérifier que les tests marqués "Q1" passent.
Note : au niveau du @SuppressWarnings, indiquez en commentaire pourquoi
on peut supprimer le warning.
-
On veut maintenant permettre de créer des graphes avec une autre valeur que null comme valeur par
par défaut pour les cases de la matrice.
À cette fin, on va créer dans Graph une nouvelle méthode of à deux paramètres,
of(nodeCount, defaultValue) qui prend le nombre de noeuds ainsi
que la valeur par défaut des cases.
On va de plus implanter la méthode getWeight(src, dst) qui renvoie la valeur
de poids d'un arc (src, dst) ou defaultValue si l'arc n'existe pas.
Pour cela, indiquer comment trouver la case (i, j) dans un tableau à une seule
dimension de taille nodeCount * nodeCount.
Implanter les méthodes of et getWeight.
Vérifier que les tests marqués "Q2" passent.
Note : il existe une méthode
Arrays.fill()
qui permet d'initialiser un tableau.
Rappel : Java initialise déjà les tableaux à null. Initialiser un tableau est TRÈS lent
(plein d'écritures), donc faites-le uniquement si c'est nécessaire.
-
On souhaite écrire la méthode addEdge(src, dst, weight) qui permet d'ajouter
un arc (src, dst) avec le poids weight. Le poids ne peut pas être null
et ne peut pas être le poids par défaut.
Vérifier que les tests marqués "Q3" passent.
-
On souhaite maintenant écrire une méthode edges(src) qui renvoie un itérable
sur les arcs du graphe qui ont pour source src.
Pour cela, nous allons définir une classe Edge à l'intérieur de l'interface Graph
qui contient les deux nœuds de l'arc (src et dst) et le poids (weight).
Rappeler ce qu'est un itérable ? Combien de méthodes abstraites y a-t-il dans l'interface Iterable ?
Comment allons-nous implanter notre Itérable ?
Comment allons-nous implanter l'interface Iterator ? Quels sont les champs ?
On ne veut pas visiter les arcs qui n'existent pas (ceux qui ont un poids égal à la valeur par défaut). Cela veut dire que notre itérateur doit visiter les voisins d'un nœud en
sautant les arcs qui n'ont pas de poids. Pour ça, le plus simple est d'écrire
une fonction qui le voisin courant en paramètre et renvoie le voisin suivant,
avec une boucle pour passer les arcs qui n'ont pas de poids.
Écrire la méthode edges(src).
Vérifier que les tests marqués "Q4" passent.
-
On souhaite pouvoir supprimer des arcs du graphe et on va implanter cette méthode au niveau de l'itérateur
(ainsi la sémantique est bien définie).
Quelle méthode doit-on implanter ? Comment fonctionne-t-elle ?
Modifier votre implantation et vérifier que les tests "Q5" passent.
-
On souhaite maintenant écrire une méthode edgeStream(src) qui renvoie un Stream
des arcs du graphe qui ont pour source src.
Pour l'instant, on ne va implanter que la partie séquentielle (non parallèle) de l'API,
donc on peut écrire le Spliterator en utilisant la syntaxe des classes anonymes.
Quelles sont les champs dont on a besoin ? Comment écrire la méthode tryAdvance ?
Comment écrire la méthode trySplit ? Comment écrire la méthode estimateSize ?
Quelles sont les caractéristiques ? Comment écrire la méthode characteristics ?
Écrire la méthode edgeStream(src).
Vérifier que les tests marqués "Q6" passent.
-
Pour les plus balèzes, on veut maintenant que lorsque l'on demande la version parallèle du Stream,
il utilise plusieurs threads. Pour cela, il faut implanter la méthode trySplit. Et pour cela, comme chaque Spliterator ne va visiter qu'une partie
des arcs, on a besoin d'un index de début et d'un index de fin.
On se propose d'implanter la méthode d'aide (helper method)
edgeSpliterator(src, start, end) qui renvoie un Spliterator
qui va visiter les arcs du graphe entre start et end.
Écrire la méthode edgeSpliterator et modifier edgeStream
pour obtenir un Stream parallélisable.
Vérifier que les tests marqués "Q7" passent.
© Université de Marne-la-Vallée