TD 1 — Set et Map
Ensembles aléatoires, intersection, union et réductions sur un Set, puis un annuaire de téléphones en Map — corrigé complet, compilé et exécuté, avec les traces d'exécution.
Ce premier TD met en pratique les deux grandes interfaces du chapitre 1 : l'interface Set (exercice 1) et l'interface Map (exercice 2). Aucune correction n'était fournie avec le sujet : les solutions ci-dessous ont été écrites, compilées et exécutées — les sorties affichées sont de vraies traces d'exécution (pour les tirages aléatoires, elles changent à chaque lancement).
Comment utiliser cette page
Cherchez d'abord par vous-même : le corrigé n'a d'intérêt qu'après avoir essayé. Les numéros des questions sont ceux du sujet, et chaque exercice se termine par la classe complète, prête à copier dans votre IDE.
Exercice 1 — l'interface Set
Le sujet rappelle d'abord les méthodes de l'interface Set :
| Méthode | Rôle |
|---|---|
boolean add(Object o) | ajoute l'élément s'il n'est pas déjà présent |
boolean addAll(Collection c) | ajoute tous les éléments non déjà présents |
boolean isEmpty() | vrai si l'ensemble est vide |
boolean contains(Object o) | vrai si l'élément est contenu |
boolean containsAll(Collection c) | vrai si tous les éléments y sont |
Iterator iterator() | renvoie un itérateur sur les éléments |
boolean remove(Object o) | supprime l'élément s'il est présent |
boolean removeAll(Collection c) | supprime tous les éléments de c |
boolean retainAll(Collection c) | ne retient que les éléments de c |
int size() | nombre d'éléments |
Q1 — deux ensembles de 5 entiers aléatoires
Random r = new Random();
Set<Integer> ens1 = new HashSet<>();
Set<Integer> ens2 = new HashSet<>();
for (int i = 0; i < 5; i++) {
ens1.add(r.nextInt(100) + 1); // nextInt(100) donne 0..99 → +1 donne 1..100
ens2.add(r.nextInt(100) + 1);
}Le premier piège
Deux tirages peuvent tomber sur la même valeur : le Set refuse alors le doublon, et
l'ensemble se retrouve avec moins de 5 éléments. La question suivante règle le problème
avec une boucle qui attend d'en avoir le compte.
Un tirage possible :
ens1 = [82, 54, 39, 91, 47]
ens2 = [97, 5, 55, 9, 91]Q2 — 980 entiers différents entre 1 et 1000
Deux approches, toutes les deux correctes :
- Tirer au hasard et compter sur le refus des doublons : on boucle tant que l'ensemble n'a pas 980 éléments ;
- Mélanger la liste
1..1000avecCollections.shuffle, puis garder les 980 premiers.
// approche 1 — la boucle « tirage avec refus »
Set<Integer> grand = new HashSet<>();
while (grand.size() < 980) {
grand.add(r.nextInt(1000) + 1);
}
// approche 2 — le mélange
List<Integer> tous = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
tous.add(i);
}
Collections.shuffle(tous);
Set<Integer> grand2 = new HashSet<>(tous.subList(0, 980));tirage : grand.size() = 980
mélange : grand2.size() = 980L'approche 1 est la plus directe ; l'approche 2 garantit un coût linéaire, quelle que soit la chance aux tirages.
Q3 — afficher un ensemble, avec un itérateur puis un foreach
Le format attendu est : le nombre d'éléments, puis chaque élément.
System.out.println(ens1.size()); // 5
Iterator<Integer> it = ens1.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
for (Integer v : ens1) { // même parcours, écriture plus courte
System.out.print(v + " ");
}
System.out.println();
System.out.println(ens1); // le toString fait le parcours pour nous5
itérateur : 82 54 39 91 47
foreach : 82 54 39 91 47
toString : [82, 54, 39, 91, 47]Peut-on se passer d'itérateur et de foreach ? Non : un Set n'a pas d'indice, donc pas
de get(i) comme sur une liste. Le toString() (ou forEach(), ou un Stream) fonctionne, mais
c'est toujours un parcours délégué — on ne fait que déplacer l'itération ailleurs.
Q4 — l'intersection
// avec un foreach
Set<Integer> inter = new HashSet<>();
for (Integer v : ens1) {
if (ens2.contains(v)) { // contains s'appuie sur equals
inter.add(v);
}
}
// sans foreach : retainAll, sur une copie d'ens1
Set<Integer> inter2 = new HashSet<>(ens1);
inter2.retainAll(ens2);intersection (foreach) = [91]
intersection (retainAll) = [91]retainAll modifie l'ensemble
inter2.retainAll(ens2) garde dans inter2 les éléments présents dans ens2 — c'est une
modification sur place. Sans la copie new HashSet<>(ens1), c'est ens1 lui-même qui
aurait été amputé.
Q5 — l'union
Set<Integer> union = new HashSet<>(ens1);
union.addAll(ens2); // les doublons éventuels sont ignorésunion = [97, 82, 5, 54, 39, 55, 9, 91, 47]Q6 — un Stream filtré
Stream<Integer> sup5 = ens1.stream().filter(v -> v > 5);
sup5.forEach(v -> System.out.print(v + " "));éléments > 5 : 82 54 39 91 47Comment afficher les éléments du Stream ? Avec forEach — c'est l'opération terminale qui
consomme le flux. Attention : un Stream ne se parcourt qu'une fois ; le réafficher demande un
nouveau ens1.stream() (ou de passer par collect pour le matérialiser).
Q7 — max, min et somme
// avec un itérateur
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int somme = 0;
Iterator<Integer> it = ens1.iterator();
while (it.hasNext()) {
int v = it.next();
if (v > max) max = v;
if (v < min) min = v;
somme += v;
}
// avec un foreach : même logique, écriture plus courte
for (int v : ens1) {
max = Math.max(max, v);
min = Math.min(min, v);
somme += v;
}
// avec un Stream : un calcul par ligne
int maxS = ens1.stream().max(Integer::compare).orElseThrow();
int minS = ens1.stream().min(Integer::compare).orElseThrow();
int sommeS = ens1.stream().mapToInt(Integer::intValue).sum();boucle : max = 91, min = 39, somme = 313
stream : max = 91, min = 39, somme = 313
Collections.max/min : 91 / 39Pourquoi mapToInt ?
sum() n'existe pas sur un Stream<Integer> : il faut d'abord le convertir en IntStream
avec mapToInt. Sur ce flux numérique, sum(), average(), max() et min() sont
disponibles directement. Et pour le max/min d'une simple collection, la classe
Collections fait le travail : Collections.max(ens1).
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.stream.Stream;
/** Exercice 1 du TD 1 — l'interface Set : les 7 questions dans un seul main. */
public class Ensembles {
/** Tire au hasard n entiers différents entre 1 et borneMax (add refuse les doublons). */
static Set<Integer> tirage(Random r, int n, int borneMax) {
Set<Integer> s = new HashSet<>();
while (s.size() < n) {
s.add(r.nextInt(borneMax) + 1);
}
return s;
}
public static void main(String[] args) {
Random r = new Random();
// Q1 — deux ensembles de 5 entiers entre 1 et 100
Set<Integer> ens1 = tirage(r, 5, 100);
Set<Integer> ens2 = tirage(r, 5, 100);
System.out.println("Q1 ens1 = " + ens1);
System.out.println("Q1 ens2 = " + ens2);
// Q2 — 980 entiers différents entre 1 et 1000
Set<Integer> grand = tirage(r, 980, 1000);
System.out.println("Q2 tirage : grand.size() = " + grand.size());
List<Integer> tous = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
tous.add(i);
}
Collections.shuffle(tous);
Set<Integer> grand2 = new HashSet<>(tous.subList(0, 980));
System.out.println("Q2 mélange : grand2.size() = " + grand2.size());
// Q3 — nombre d'éléments, puis parcours avec un itérateur, puis un foreach
System.out.println("Q3 " + ens1.size());
Iterator<Integer> it = ens1.iterator();
StringBuilder viaIterateur = new StringBuilder();
while (it.hasNext()) {
viaIterateur.append(it.next()).append(' ');
}
System.out.println("Q3 itérateur : " + viaIterateur.toString().trim());
StringBuilder viaForeach = new StringBuilder();
for (Integer v : ens1) {
viaForeach.append(v).append(' ');
}
System.out.println("Q3 foreach : " + viaForeach.toString().trim());
System.out.println("Q3 toString : " + ens1);
// Q4 — intersection avec un foreach, puis sans
Set<Integer> interForeach = new HashSet<>();
for (Integer v : ens1) {
if (ens2.contains(v)) {
interForeach.add(v);
}
}
Set<Integer> interRetain = new HashSet<>(ens1);
interRetain.retainAll(ens2);
System.out.println("Q4 intersection (foreach) = " + interForeach);
System.out.println("Q4 intersection (retainAll) = " + interRetain);
// Q5 — union
Set<Integer> union = new HashSet<>(ens1);
union.addAll(ens2);
System.out.println("Q5 union = " + union);
// Q6 — Stream filtré sur les éléments strictement supérieurs à 5
Stream<Integer> sup5 = ens1.stream().filter(v -> v > 5);
StringBuilder affichage = new StringBuilder();
sup5.forEach(v -> affichage.append(v).append(' '));
System.out.println("Q6 éléments > 5 : " + affichage.toString().trim());
// Q7 — max, min, somme : boucle (itérateur/foreach), puis Stream
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int somme = 0;
for (Integer v : ens1) {
if (v > max) max = v;
if (v < min) min = v;
somme += v;
}
System.out.println("Q7 boucle : max = " + max + ", min = " + min + ", somme = " + somme);
int maxS = ens1.stream().max(Integer::compare).orElseThrow();
int minS = ens1.stream().min(Integer::compare).orElseThrow();
int sommeS = ens1.stream().mapToInt(Integer::intValue).sum();
System.out.println("Q7 stream : max = " + maxS + ", min = " + minS + ", somme = " + sommeS);
System.out.println("Q7 Collections.max/min : " + Collections.max(ens1) + " / " + Collections.min(ens1));
}
}Exercice 2 — l'interface Map
On stocke le numéro de téléphone d'amis identifiés par leur nom, dans un dictionnaire (une
Map). Le sujet précise que l'on dispose d'un type Telephone, muni d'un constructeur prenant
5 Integer, d'une méthode d'affichage, d'equals et de hashCode — et effectivement, sans
equals/hashCode corrects, les recherches dans la Map ne fonctionneraient pas (voir le
chapitre 1). Le petit type utilisé pour tester est fourni en
bas d'exercice.
Q1 — les types de la clé et de la valeur
- la clé
Kest unString: le nom de l'ami ; - la valeur
Vest unTelephone: son numéro.
Q2 — instancier la Map
Map<String, Telephone> annuaire = new HashMap<>();Comme toujours : l'interface en type de variable, l'implémentation à l'instanciation.
Q3 — insérer Pierre
annuaire.put("Pierre", new Telephone(6, 54, 67, 34, 12));Q4 — afficher tous les numéros
for (Telephone t : annuaire.values()) {
System.out.println(t);
}07 77 88 99 11
06 54 67 34 12
06 11 22 33 44
06 55 44 33 22L'ordre n'est pas celui d'insertion : un HashMap range les couples selon les hachages des
clés, et rien ne garantit cet ordre.
Q5 — les numéros des amis en « D »
for (Map.Entry<String, Telephone> couple : annuaire.entrySet()) {
if (couple.getKey().startsWith("D")) {
System.out.println(couple.getKey() + " : " + couple.getValue());
}
}Dassis : 07 77 88 99 11
Dupont : 06 11 22 33 44C'est le parcours par entrySet() — celui qui donne accès aux deux à la fois, sans double
recherche. La variante keySet() + get existe aussi (une recherche par clé).
Q6 — la méthode qui retrouve un numéro
static Telephone telephoneDe(Map<String, Telephone> annuaire, String nom) {
Telephone t = annuaire.get(nom);
if (t == null) {
throw new NoSuchElementException("Pas de numéro pour " + nom);
}
return t;
}Le contrat de get
get renvoie null quand la clé est absente (c'est dans la JavaDoc rappelée par le sujet) —
d'où le test à null avant de lever l'exception demandée, NoSuchElementException.
Q7 — les noms passés en arguments
for (String nom : args) {
System.out.println(nom + " -> " + telephoneDe(annuaire, nom));
}Exécution réelle de java Annuaire Pierre Martin Inconnu :
Pierre -> 06 54 67 34 12
Martin -> 06 55 44 33 22
Exception in thread "main" java.util.NoSuchElementException: Pas de numéro pour Inconnu
at Annuaire.telephoneDe(Annuaire.java:15)
at Annuaire.main(Annuaire.java:49)C'est le comportement demandé : dès qu'un nom est inconnu, l'exception interrompt le programme. (En production, on choisirait plutôt d'afficher le message et de continuer — mais le sujet demande bien de lever.)
Q8 — plusieurs numéros par ami
Il faut changer le type de la valeur : V passe de Telephone à List<Telephone> (un
Set<Telephone> marcherait aussi). La clé ne bouge pas. On obtient :
Map<String, List<Telephone>> annuaire = new HashMap<>();Q9 — la réécriture
L'insertion n'est plus un simple put : il faut retrouver la liste de l'ami et la créer au
premier numéro.
static Map<String, List<Telephone>> annuaire = new HashMap<>();
static void ajouter(String nom, Telephone numero) {
List<Telephone> numeros = annuaire.get(nom);
if (numeros == null) {
numeros = new ArrayList<>();
annuaire.put(nom, numeros);
}
numeros.add(numero);
}L'écriture condensée équivalente, avec computeIfAbsent : annuaire.computeIfAbsent(nom, k -> new ArrayList<>()).add(numero);. Pour la question 6, « un des téléphones possibles » :
static Telephone telephoneDe(String nom) {
List<Telephone> numeros = annuaire.get(nom);
if (numeros == null || numeros.isEmpty()) {
throw new NoSuchElementException("Pas de numéro pour " + nom);
}
return numeros.get(0); // on choisit le premier de la liste
}— Q9·4 : tous les numéros —
07 77 88 99 11
06 54 67 34 12
06 12 34 56 78
06 11 22 33 44
— Q9·5 : les amis en 'D' —
Dassis : [07 77 88 99 11]
Dupont : [06 11 22 33 44]
— Q9·6 —
Pierre -> 06 54 67 34 12
Exception in thread "main" java.util.NoSuchElementException: Pas de numéro pour Inconnu
at AnnuaireListes.telephoneDe(AnnuaireListes.java:29)
at AnnuaireListes.main(AnnuaireListes.java:60)Le piège du put
Pour ajouter un second numéro, un put(nom, numero) écraserait la liste existante — put
remplace la valeur associée à la clé et renvoie l'ancienne. L'ajout doit se faire dans
la liste, pas dans la Map.
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Exercice 2 du TD 1 — l'interface Map : annuaire de téléphones.
* Q1 à Q7 : un nom (String, la clé) -> un téléphone (Telephone, la valeur).
*/
public class Annuaire {
/** Q6 — le téléphone du nom, ou une exception si le nom est inconnu. */
static Telephone telephoneDe(Map<String, Telephone> annuaire, String nom) {
Telephone t = annuaire.get(nom);
if (t == null) {
throw new NoSuchElementException("Pas de numéro pour " + nom);
}
return t;
}
public static void main(String[] args) {
// Q2 — instanciation de la Map
Map<String, Telephone> annuaire = new HashMap<>();
// Q3 — Pierre et son numéro
annuaire.put("Pierre", new Telephone(6, 54, 67, 34, 12));
// quelques amis en plus pour la suite
annuaire.put("Dupont", new Telephone(6, 11, 22, 33, 44));
annuaire.put("Dassis", new Telephone(7, 77, 88, 99, 11));
annuaire.put("Martin", new Telephone(6, 55, 44, 33, 22));
// Q4 — tous les numéros de l'annuaire
System.out.println("— Q4 : tous les numéros —");
for (Telephone t : annuaire.values()) {
System.out.println(t);
}
// Q5 — les numéros des amis dont le nom commence par 'D'
System.out.println("— Q5 : les amis en 'D' —");
for (Map.Entry<String, Telephone> couple : annuaire.entrySet()) {
if (couple.getKey().startsWith("D")) {
System.out.println(couple.getKey() + " : " + couple.getValue());
}
}
// Q7 — les téléphones des noms passés en argument
if (args.length > 0) {
System.out.println("— Q7 : les noms passés en argument —");
for (String nom : args) {
System.out.println(nom + " -> " + telephoneDe(annuaire, nom));
}
}
}
}import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Exercice 2, questions 8 et 9 : les amis ont plusieurs numéros.
* La valeur V devient List<Telephone> (une liste par nom).
*/
public class AnnuaireListes {
static Map<String, List<Telephone>> annuaire = new HashMap<>();
/** Ajoute un numéro à un ami (crée la liste au premier numéro). */
static void ajouter(String nom, Telephone numero) {
List<Telephone> numeros = annuaire.get(nom);
if (numeros == null) {
numeros = new ArrayList<>();
annuaire.put(nom, numeros);
}
numeros.add(numero);
}
/** Q9·6 — un des téléphones possibles du nom, ou exception si inconnu. */
static Telephone telephoneDe(String nom) {
List<Telephone> numeros = annuaire.get(nom);
if (numeros == null || numeros.isEmpty()) {
throw new NoSuchElementException("Pas de numéro pour " + nom);
}
return numeros.get(0);
}
public static void main(String[] args) {
// Q9·1 et Q9·2 — même clé String, valeur List<Telephone>
ajouter("Pierre", new Telephone(6, 54, 67, 34, 12));
ajouter("Pierre", new Telephone(6, 12, 34, 56, 78)); // un second numéro
ajouter("Dupont", new Telephone(6, 11, 22, 33, 44));
ajouter("Dassis", new Telephone(7, 77, 88, 99, 11));
// Q9·4 — tous les numéros : values() renvoie des listes
System.out.println("— Q9·4 : tous les numéros —");
for (List<Telephone> numeros : annuaire.values()) {
for (Telephone t : numeros) {
System.out.println(t);
}
}
// Q9·5 — les amis en 'D'
System.out.println("— Q9·5 : les amis en 'D' —");
for (Map.Entry<String, List<Telephone>> couple : annuaire.entrySet()) {
if (couple.getKey().startsWith("D")) {
System.out.println(couple.getKey() + " : " + couple.getValue());
}
}
// Q9·6 — un des téléphones de Pierre, puis d'un inconnu
System.out.println("— Q9·6 —");
System.out.println("Pierre -> " + telephoneDe("Pierre"));
System.out.println("Inconnu -> " + telephoneDe("Inconnu"));
}
}import java.util.Objects;
/**
* Le type « Telephone » du sujet : un constructeur à 5 entiers (le numéro
* en 5 groupes), un affichage, une méthode equals et une méthode hashCode.
*/
public class Telephone {
private final int a, b, c, d, e;
public Telephone(int a, int b, int c, int d, int e) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
}
@Override
public String toString() {
return String.format("%02d %02d %02d %02d %02d", a, b, c, d, e);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Telephone)) return false;
Telephone t = (Telephone) o;
return a == t.a && b == t.b && c == t.c && d == t.d && e == t.e;
}
@Override
public int hashCode() {
return Objects.hash(a, b, c, d, e);
}
}td1-collections.pdf
R3.02 · TD 1 — Set et Map · 2 pages
Chargement du document…
Dernière mise à jour le
Ch. 2 — Piles et files
Deux conteneurs qui ne laissent accéder qu'à un seul élément : la pile (dernier arrivé, premier sorti) et la file (premier arrivé, premier sorti) — parcours de graphes et calcul d'expressions.
TD 2 — Piles et files
Trois exercices — la pile sur tableau, la file circulaire, et la vérification des délimiteurs — corrigés, compilés et testés, avec les traces d'exécution.