Phase 3Structures de données

#12 Collections

Vec, HashMap, itérateurs

Les vecteurs (Vec<T>)

Un Vec<T> est un tableau dynamique : il peut grandir ou rétrécir pendant l'exécution du programme. C'est la collection la plus utilisée en Rust.

Créer et manipuler un Vec
fn main() {
    // Créer un vecteur vide
    let mut nombres: Vec<i32> = Vec::new();
    nombres.push(1);
    nombres.push(2);
    nombres.push(3);

    // Raccourci avec la macro vec!
    let fruits = vec!["pomme", "banane", "cerise"];

    println!("Fruits : {:?}", fruits);
    println!("Nombre de fruits : {}", fruits.len());

    // Accéder aux éléments
    let premier = &fruits[0];        // panique si hors limites
    let deuxieme = fruits.get(1);    // retourne Option<&T>
    println!("Premier : {}", premier);
    println!("Deuxième : {:?}", deuxieme);
}

Opérations courantes sur Vec

Opérations sur les vecteurs
let mut v = vec![1, 2, 3, 4, 5];

v.push(6);          // ajouter à la fin
v.pop();             // retirer le dernier -> Some(6)
v.insert(0, 0);     // insérer à l'index 0
v.remove(0);         // retirer l'élément à l'index 0
v.contains(&3);      // true
v.sort();            // trier sur place
v.reverse();         // inverser l'ordre

Les HashMaps

Un HashMap<K, V> stocke des paires clé-valeur. Il faut l'importer depuis std::collections.

Utiliser un HashMap
use std::collections::HashMap;

fn main() {
    let mut populations = HashMap::new();

    // Insérer des paires clé-valeur
    populations.insert("Paris", 2_161_000);
    populations.insert("Lyon", 516_000);
    populations.insert("Marseille", 870_000);

    // Accéder à une valeur
    if let Some(pop) = populations.get("Paris") {
        println!("Population de Paris : {}", pop);
    }

    // Modifier une valeur
    populations.insert("Lyon", 520_000); // remplace l'ancienne

    // Entry : insérer seulement si absent
    populations.entry("Toulouse").or_insert(486_000);

    // Parcourir toutes les entrées
    for (ville, pop) in &populations {
        println!("{} : {} habitants", ville, pop);
    }
}

Les itérateurs

Les itérateurs sont au coeur de Rust. Chaque collection peut être transformée en itérateur avec .iter(), .iter_mut() ou.into_iter().

Les trois types d'itérateurs
let nombres = vec![1, 2, 3];

// iter() : emprunte les éléments (&T)
for n in nombres.iter() {
    println!("{}", n);
}

// iter_mut() : emprunte de manière mutable (&mut T)
let mut nombres = vec![1, 2, 3];
for n in nombres.iter_mut() {
    *n *= 2;
}
// nombres est maintenant [2, 4, 6]

// into_iter() : consomme le vecteur (T)
let nombres = vec![1, 2, 3];
for n in nombres.into_iter() {
    println!("{}", n);
}
// nombres n'est plus utilisable ici

Méthodes courantes (map, filter, collect)

Les itérateurs proposent des méthodes fonctionnelles puissantes pour transformer et filtrer les données.

map, filter, collect
fn main() {
    let nombres = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // filter : garder seulement les pairs
    // map    : les doubler
    // collect: rassembler dans un nouveau Vec
    let pairs_doubles: Vec<i32> = nombres
        .iter()
        .filter(|&&n| n % 2 == 0)
        .map(|&n| n * 2)
        .collect();

    println!("Nombres pairs doublés : {:?}", pairs_doubles);
    // [4, 8, 12, 16, 20]

    // Autres méthodes utiles
    let somme: i32 = nombres.iter().sum();
    let produit: i32 = nombres.iter().product();
    let max = nombres.iter().max();   // Some(&10)
    let min = nombres.iter().min();   // Some(&1)
    let any_pair = nombres.iter().any(|&n| n % 2 == 0); // true
    let all_positifs = nombres.iter().all(|&n| n > 0);   // true
}

À vous de jouer

Essayez les commandes ci-dessous pour compiler et exécuter le programme :

terminal — cargo
user@stemlegacy:~/collections-demo$