Aller au contenu

HashMap en Rust

Une HashMap stocke des données sous forme de paires clé-valeur : vous retrouvez une valeur grâce à sa clé, et non grâce à sa position comme dans un tableau ou un vecteur. Si vous avez déjà utilisé un dictionnaire en Python ou un objet utilisé comme table en JavaScript, c’est la même idée.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
println!("{:?}", ages);
}

La ligne use std::collections::HashMap; en haut du fichier rend le type disponible dans la portée. Contrairement à Vec et String, HashMap n’est pas disponible par défaut : cette ligne est donc obligatoire.

▶ Essayez par vous-même
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
match ages.get("Alice") {
Some(age) => println!("Alice a {age} ans"),
None => println!("Introuvable"),
}
}
▶ Essayez par vous-même

.get() renvoie une Option plutôt que la valeur directement, car la clé n’existe peut-être pas. Nous verrons Option en détail sur sa propre page. Pour l’instant, un match avec Some/None est la façon habituelle de la traiter.

Insérer à nouveau avec la même clé remplace l’ancienne valeur :

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Alice", 31); // remplace 30
println!("{:?}", ages.get("Alice"));
}
▶ Essayez par vous-même

Si vous voulez insérer une valeur seulement quand la clé n’existe pas encore, utilisez .entry() :

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.entry("Alice").or_insert(99); // Alice existe déjà, donc rien ne se passe
ages.entry("Bob").or_insert(25); // Bob n'existe pas, donc il est inséré
println!("{:?}", ages);
}
▶ Essayez par vous-même
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
for (name, age) in &ages {
println!("{name} a {age} ans");
}
}
▶ Essayez par vous-même

Pensez à une HashMap chaque fois que vous voulez retrouver des données par un nom ou un identifiant plutôt que par une position : compter la fréquence des mots, associer des noms d’utilisateur à leurs données, mettre des résultats en cache par clé. Si l’ordre compte ou si vous parcourez simplement une liste, un Vec est plus adapté.

Stockez trois noms et numéros de téléphone dans une HashMap. Cherchez ensuite "Ben" (qui y figure) et "Dana" (qui n’y figure pas), et affichez une phrase sympathique pour chacun. Indice : parcourez ["Ben", "Dana"] avec une boucle et faites un match sur .get(name).

Voir la solution
use std::collections::HashMap;
fn main() {
let mut phone_book = HashMap::new();
phone_book.insert("Ana", "555-1234");
phone_book.insert("Ben", "555-9876");
phone_book.insert("Chen", "555-4321");
for name in ["Ben", "Dana"] {
match phone_book.get(name) {
Some(number) => println!("{name} : {number}"),
None => println!("{name} : absent de l'annuaire"),
}
}
}

Sortie :

Ben : 555-9876
Dana : absent de l'annuaire
▶ Essayez par vous-même

Parcourir les noms qui nous intéressent, plutôt que la table elle-même, nous donne en plus un ordre fixe. C’est une astuce pratique chaque fois que vous avez besoin d’un affichage prévisible à partir d’une HashMap.

Vous avez les votes ["rouge", "bleu", "rouge", "vert", "bleu", "rouge"]. Comptez combien chaque couleur en a reçu, puis affichez les totaux pour "rouge", "vert", "bleu" et "rose" (qui n’en a reçu aucun). Indice : counts.entry(color).or_insert(0) vous donne le compteur actuel de cette couleur, en partant de 0. Ajoutez-lui un avec *count += 1.

Voir la solution
use std::collections::HashMap;
fn main() {
let votes = ["rouge", "bleu", "rouge", "vert", "bleu", "rouge"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["rouge", "vert", "bleu", "rose"] {
match counts.get(color) {
Some(n) => println!("{color} : {n}"),
None => println!("{color} : 0"),
}
}
}

Sortie :

rouge : 3
vert : 1
bleu : 2
rose : 0
▶ Essayez par vous-même

or_insert(0) renvoie une référence mutable vers la valeur stockée dans la table, pas une copie. Le * de *count += 1 signifie « modifier le nombre vers lequel ceci pointe », donc c’est bien la table elle-même qui est mise à jour. Ce motif « entry, sinon insérer 0, puis ajouter 1 » est la façon habituelle de compter des choses en Rust.

  • HashMap<K, V> stocke des paires clé-valeur ; rendez-la disponible avec use std::collections::HashMap;.
  • .insert(key, value) ajoute ou remplace ; .get(key) cherche une valeur et renvoie une Option.
  • .entry(key).or_insert(value) n’insère que si la clé n’est pas déjà présente.
  • L’ordre de parcours n’est pas garanti : ne comptez pas dessus.

Petit quiz

1. Que faut-il pour rendre HashMap disponible, contrairement à Vec ou String ?

2. Que fait .entry(key).or_insert(value) ?

3. Pouvez-vous compter sur l'ordre d'affichage des entrées quand vous parcourez une HashMap ?

Score : 0 / 3