POO, Java - Ex 4. Collections

publicité
Master 2 BBSG
POO, langage Java
Laurent Tichit
5. Collections et autres structures de données
1.
2.
3.
4.
5.
Listes triées
Table associative : un annuaire
Affichage de l’environnement d’un programme
Arborescences
Graphes
AVERTISSEMENT. Les classes et interfaces liées aux collections, subissent à partir de
la version 5 de Java une importante modification, puisqu’elles deviennent
« génériques ». Cela se manifeste, notamment dans la documentation en ligne, par
une notation spéciale : le signe <E> (comme « Element ») à côté du nom, qui
représente un type (une interface, ou plus rarement une classe) :
public interface Collection<E> {
...
}
A cause de la généricité, si vous utilisez des collections d' Object et non pas des
collections génériques avec Java5 (et supérieur), vous risquez d’obtenir des
avertissements concernant des opérations « incontrôlées » ou « dangereuses » :
Note: MachinTruc.java uses unchecked or unsafe operations.
Pour éviter ces messages, soit vous utilisez les collections génériques, soit (vous
vous contentez de rester dans la préhistoire) il vous suffit de compiler votre fichier
source en indiquant qu’il relève de Java 1.4 :
javac -source 1.4 <fichiers_à_compiler>
(la version 1.4 précède immédiatement la version 5). Utilisateurs d’Eclipse, pour
obtenir le même résultat vous devez aller dans Project > Properties > Java Compiler,
cocher la case Enable project specific settings et jouer sur l’indication du champ
Compiler compliance level.
POURQUOI ? Pour des raisons de sécurité, la machine virtuelle Java 1.4 (et les
précédentes) teste les types des éléments insérés dans les collections (et extraits de
celles-ci). Ces tests ont lieu à l'exécution (on parle alors de tests dynamiques) si on
utilise Java 1.4 (ou inférieur) car à la compilation, il est impossible de connaître le
type des données – elles sont vues simplement comme des Object. Si on utilise les
version génériques (Java 5+), ces tests peuvent (enfin!) avoir lieu à la compilation
(tests statiques) car on déclare le type des éléments => meilleures performances ! Si
on utilise les collections non-génériques avec Java5+, il faut donc le déclarer !
5.1. Listes triées
Écrivez un programme qui construit une collection triée contenant n nombres entiers (représentés
par des objets Integer) tirés au hasard dont la valeur est comprise entre 0 et 1000. A votre choix,
la valeur de n est lue au début de l’exécution ou est un argument du programme. Ensuite, ce dernier
affiche la collection construite afin qu’on puisse constater qu’elle est bien triée.
A. Dans la première version du programme la collection est une sorte de List<Integer> (par
exemple un ArrayList ou une LinkedList) que vous triez, après la construction, en utilisant
une méthode statique ad hoc de la classe Collections.
B. Dans une deuxième version, la collection est une sorte de Set<Integer> (c’est-à-dire un
HashSet ou un TreeSet, mais avez-vous le choix ?) si bien qu’elle est constamment triée.
5.2. Table associative : un annuaire
On vous demande d’écrire une classe Annuaire pour mémoriser des numéros de téléphone et
d’adresses. Chaque entrée est représentée par une fiche à plusieurs champs : un nom, un numéro et
une adresse. La structure des fiches est décrite par une classe Fiche que vous devez écrire.
Écrivez
également
une classe
Annuaire
comportant
une
table
(Map<String,Fiche>) qui sera faite d’associations ( un_nom , une_fiche ).
associative
A. Dans un premier temps, la table associative en question sera une instance de la classe HashMap.
Écrivez un programme répétant les opérations suivantes
• lecture d’une « commande » d’une des formes : +nom, ?nom, ! ou bye,
• si la commande a la forme ?nom, recherche et affiche la fiche concernant le nom indiqué,
• si la commande est de la forme +nom, saisie des autres informations d’une fiche associée à
ce nom et insertion de la fiche correspondante dans l’annuaire,
• si la commande est !, affichage de toutes les fiches de l’annuaire,
• si la commande est . (un point), arrêt du programme.
B. On constate que la commande ! produit l’affichage des fiches dans un ordre imprévisible. Que
faut-il changer dans le programme précédent pour que les fiches apparaissent dans l’ordre des
noms ?
C. Faites en sorte qu’à la fin (resp. au début) du programme l’annuaire soit enregistré (resp. lu) dans
un fichier nommé annuaire.obj. Utilisez des flux ObjectInputStream et
ObjectOutputStream (que l’on doit créer à partir de flux FileInputStream et
FileOutputStream préexistants).
N’oubliez pas d’autoriser (par un énoncé « implements Serializable ») la « sérialisation »
des objets qui doivent être écrits ou lus dans le fichier (voir cours).
5.3. Affichage de l’environnement d’un programme
Les applications Java accèdent à un ensemble de « propriétés système » qui définissent des aspects
de leur environnement d’exécution, comme la version de la machine Java, le système d’exploitation
sous-jacent, le répertoire de travail, etc. Ces propriétés sont codées sous forme de couples de
chaînes (clé, valeur), et on les obtient par un appel System.getProperties() qui renvoie un
objet Properties qui est une variété de table associative (Map). En fait, de Map<Object,
Object> depuis Java5. Donc il ne sert à rien de se servir des Generics ici.
Pour connaître la valeur d’une
System.getProperty(nom).
propriété
à
partir
de
son
nom
on
utilise
De manière analogue, une application Java accède à l’ensemble de variables d’environnement
(path, user, etc.) définies au niveau du système d’exploitation sous-jacent ; c’est encore une table
associative (Map) qu’on obtient par un appel de System.getenv(). Cette fois-ci, c'est une
Map<String, String>.
Pour obtenir la valeur d’une variable d’environnement a partir de son nom on écrit
System.getenv(nom).
Écrivez une méthode (qui servira pour afficher les deux types de Map)
static void afficherMap(Map tableAssoc);
qui affiche les éléments d’une table associative sous la forme
user.dir --> C:\_\JAtelier\Atelier
java.vm.version --> 1.6.0-b18
os.name --> Windows XP
user.home --> C:\Documents and Settings\Laulo
etc.
Servez-vous en pour obtenir la liste des propriétés système avec leur valeurs, puis des variables
d’environnement avec leurs valeurs.
N.B. Properties est une sous-classe de Hashtable, elle-même sous-classe de Dictionary.
N’investissez pas dans Dictionary : elle est obsolète.
5.4. Arborescences
Une arborescence (avec moins de rigueur on dit parfois arbre) est une structure de données formée
d’un ensemble E et d’une relation qui à chaque élément de E – sauf un, appelé la racine de
l’arborescence – associe un autre élément appelé son père.
Les éléments d’une arborescence sont appelés nœuds ; à chaque nœud n est donc associée une liste,
éventuellement vide, de nœuds dont n est le père ; on les appelle les fils de n. Lorsqu’un nœud n’a
pas de fils, on dit que c’est une feuille.
Pour fixer les idées nous supposerons ici que les informations portées par les nœuds sont des
chaînes de caractères.
A. Écrivez une classe Noeud pour représenter les [nœuds des] arborescences. Elle aura deux
variables privées
• info, de type String, pour représenter l’information portée par le nœud,
• fils, de type ArrayList<Noeud>, pour représenter la liste des fils du nœud.
et les méthodes publiques suivantes :
• Noeud(String info) – construction d’un nœud portant l’information indiquée,
• String info() – renvoie l’information portée par le nœud,
• boolean estFeuille() – vrai si et seulement si le nœud n’a pas de fils,
• void ajouterFils(Noeud fils) – ajoute le nœud indiqué comme fils du nœud en
question,
• Iterator fils() – renvoie un itérateur permettant de parcourir la liste des fils du
nœud ; le comportement est indéfini si le nœud est une feuille,
• void afficher() – affiche le nœud et tous ses descendants (ses fils, les fils de ses fils,
etc.) à raison de un nœud par ligne ; la hiérarchie (c.-à-d. « qui est fils de qui ? ») est
exprimée par une marge à gauche de, par exemple, trois espaces par niveau.
B. Pour essayer la classe Noeud, écrivez une classe de test qui construit et affiche l’arborescence
des fichiers et répertoires ayant pour racine le répertoire de travail.
Indications. Comme cela a été vu à l’exercice précédent, le nom du répertoire de travail peut être
obtenu par l’expression System.getProperty("user.dir");
Pour vous promener dans les fichiers et répertoires, les méthodes suivantes de la classe
java.io.File vous seront utiles : getName(), isDirectory(), listFiles().
C. Pour améliorer votre conception, transformez Noeud en classe abstraite, et créez deux classes
filles NoeudInterne et Feuille. Supprimez donc la méthode estFeuille() et ses appels,
la méthode ajouterFils() est seulement présente dans un NoeudInterne. Modifiez
éventuellement votre classe de test.
5.5. Représentation des graphes
Un graphe non orienté G = (S, A) est déterminé par la donnée d’un ensemble S, dont les éléments
sont appelés les sommets, et un ensemble A de paires de sommets dont les éléments sont appelés
arêtes. Si a = {s1, s2} est une arête on dit que s1 et s2 sont les extrémités de a et donc que s1 et s2 sont
deux sommets adjacents (on dit aussi voisins).
Souvent on peut représenter S comme un ensemble de points du plan et alors on représente A
comme un ensemble de segments ayant ces sommets pour extrémités (il découle de la définition que
nous avons donnée qu’il ne peut pas y avoir deux arêtes distinctes ayant les mêmes extrémités).
Dans certaines applications on associe un poids à chaque arête : par exemple, si le graphe représente
un réseau routier, alors chaque arête correspond à un tronçon de route entre deux carrefours et est
naturellement affectée d’un poids : la longueur du tronçon :
Dans le programme réalisé ici nous allons coder le graphe en associant à chaque sommet s la liste
des couples (s’, p) où s’ est un sommet adjacent à s et p le poids de l’arête correspondante. Par
exemple, dans la figure ci-dessus, pour le sommet sa (le sommet étiqueté a) cette liste est [(sf, 5),
(sb, 3)], pour le sommet sb la liste est [(sa, 3), (sf, 6), (se, 4 ), (sc, 1)] et ainsi de suite.
Écrivez la classe Voisin dont les instances représentent les couples (sommet, poids). Faites
simple, ce n’est qu’une classe auxiliaire.
Écrivez la classe Sommet dont les instances représentent les sommet du graphe. Elle comporte
deux variables d’instance privées, étiquette (de type String) et voisins (de type ArrayList) et
des méthodes publiques
• Sommet(String etiquette) – constructeur d’un sommet portant l’étiquette
indiquée,
• boolean estVoisin(Sommet sommet) – a.estVoisin(b) est vrai si et seulement si a
et b sont voisins,
• void ajouterVoisin(Sommet sommet, int poids) – ajout d’un élément à la
liste des voisins du sommet en question,
• Iterator<Voisin> voisins() – obtention d’un térateur pour parcourir la liste des
voisins du sommet en question,
• String toString() – obtention d’une forme textuelle du sommet (en fait, obtention de
l’étiquette du sommet).
Écrivez la classe Graphe, composée d’une table associative (Map) dont les valeurs sont les
sommets du graphe et les clés les étiquettes correspondantes. Il y aura les méthodes publiques :
• Sommet chercherSommet(String etiquette) – obtention du sommet ayant
l’étiquette indiquée, ou null si un tel sommet n’existe pas,
• Sommet obtenirSommet(String etiquette) – obtention d’un sommet ayant
l’étiquette indiquée, nouvellement créé si nécessaire,
• boolean ajouterArete(Sommet sommet1, Sommet sommet2, int
poids) – ajouter l’arête ayant pour extremités les sommets indiqués
• boolean ajouterArete(String etiquette1, String etiquette2,
int poids) – même chose que la précédente, mais à partir des étiquettes des sommets,
• void chemins(String depart, String arrivee) – affichage de tous les
chemins joignant les sommets ayant les étiquettes indiquées.
Pour essayer tout cela, écrivez un programme principal qui crée un graphe analogue à celui de la
figure ci-dessus et, par exemple, affiche tous les chemins joignant le sommet a au sommet j.
Indication. Pour la fonction chemins vous pouvez écrire une fonction récursive, auxiliaire
private void chemins(Sommet depart, Sommet arrivee, Stack<Sommet>
pile);
qui, si le problème n’est pas résolu (i.e. si départ n’est pas égal à arrivée), parcourt les sommets
voisins de départ et se rappelle elle même avec chacun de ces sommets pour nouveau départ. La
pile donnée représente le bout de chemin déjà construit ; elle sert à l’affichage de la solution et aussi
à vérifier que le chemin construit n’a pas de cycles.
Enfin, en lisant la doc de Stack, vous vous rendez-compte qu'elle est obsolète. Remplacez là par
une Double-Ended Queue Deque.
Téléchargement