00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

HashMap in Rust

La HashMap e una delle strutture dati piu utilizzate in Rust. Fa parte della libreria standard e permette di memorizzare coppie chiave-valore con accesso in tempo medio costante O(1). Per utilizzarla, occorre importarla dal modulo std::collections.

Importazione e Creazione

Per usare una HashMap bisogna prima importarla:

use std::collections::HashMap;

fn main() {
    // Creazione di una HashMap vuota
    let mut punteggi: HashMap<String, i32> = HashMap::new();

    // Creazione con capacita iniziale per ottimizzare le allocazioni
    let mut mappa: HashMap<&str, i32> = HashMap::with_capacity(10);
}

Si puo anche creare una HashMap a partire da un iteratore di tuple:

use std::collections::HashMap;

fn main() {
    let coppie = vec![("mela", 3), ("banana", 5), ("arancia", 2)];
    let frutta: HashMap<&str, i32> = coppie.into_iter().collect();

    println!("{:?}", frutta);
}

Inserimento, Lettura e Rimozione

Le operazioni fondamentali su una HashMap sono insert, get e remove.

use std::collections::HashMap;

fn main() {
    let mut capitali: HashMap<&str, &str> = HashMap::new();

    // Inserimento
    capitali.insert("Italia", "Roma");
    capitali.insert("Francia", "Parigi");
    capitali.insert("Germania", "Berlino");

    // Lettura con get (restituisce Option<&V>)
    match capitali.get("Italia") {
        Some(capitale) => println!("La capitale dell'Italia e {}", capitale),
        None => println!("Nazione non trovata"),
    }

    // Verifica esistenza di una chiave
    if capitali.contains_key("Francia") {
        println!("La Francia e presente nella mappa");
    }

    // Rimozione (restituisce Option<V>)
    let rimossa = capitali.remove("Germania");
    println!("Rimossa: {:?}", rimossa); // Some("Berlino")

    println!("Mappa attuale: {:?}", capitali);
}

La Entry API

La Entry API e uno degli strumenti piu potenti della HashMap. Permette di gestire in modo efficiente l’inserimento condizionato di valori.

use std::collections::HashMap;

fn main() {
    let mut punteggi: HashMap<&str, i32> = HashMap::new();

    // or_insert: inserisce il valore solo se la chiave non esiste
    punteggi.entry("Alice").or_insert(50);
    punteggi.entry("Alice").or_insert(100); // Non sovrascrive, Alice resta 50

    // or_insert_with: inserisce usando una closure (utile per valori costosi da calcolare)
    punteggi.entry("Bob").or_insert_with(|| {
        println!("Calcolo punteggio iniziale per Bob...");
        75
    });

    // or_default: inserisce il valore di default del tipo (0 per i32)
    punteggi.entry("Carlo").or_default();

    println!("{:?}", punteggi);
}

La Entry API e particolarmente utile per modificare valori esistenti:

use std::collections::HashMap;

fn main() {
    let mut contatori: HashMap<&str, i32> = HashMap::new();

    let parole = vec!["ciao", "mondo", "ciao", "rust", "mondo", "ciao"];

    for parola in &parole {
        let contatore = contatori.entry(parola).or_insert(0);
        *contatore += 1;
    }

    println!("{:?}", contatori);
    // {"ciao": 3, "mondo": 2, "rust": 1}
}

Iterare su una HashMap

Si puo iterare sulle coppie chiave-valore, solo sulle chiavi o solo sui valori.

use std::collections::HashMap;

fn main() {
    let mut voti: HashMap<&str, Vec<i32>> = HashMap::new();
    voti.insert("Matematica", vec![8, 7, 9]);
    voti.insert("Italiano", vec![7, 6, 8]);
    voti.insert("Inglese", vec![9, 9, 8]);

    // Iterare su coppie chiave-valore
    for (materia, lista_voti) in &voti {
        let media: f64 = lista_voti.iter().sum::<i32>() as f64 / lista_voti.len() as f64;
        println!("{}: media {:.1}", materia, media);
    }

    // Solo chiavi
    for materia in voti.keys() {
        println!("Materia: {}", materia);
    }

    // Solo valori
    for lista in voti.values() {
        println!("Voti: {:?}", lista);
    }
}

Ownership e HashMap

Quando si inseriscono valori in una HashMap, la ownership viene trasferita alla mappa per i tipi che implementano Copy viene fatta una copia, per gli altri il valore viene spostato.

use std::collections::HashMap;

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

    let chiave = String::from("linguaggio");
    let valore = String::from("Rust");

    mappa.insert(chiave, valore);

    // chiave e valore non sono piu utilizzabili qui
    // println!("{}", chiave); // ERRORE: valore spostato

    // Con i riferimenti si mantiene la ownership
    let mut mappa_ref: HashMap<&str, &str> = HashMap::new();
    let nome = String::from("Rust");
    mappa_ref.insert("linguaggio", &nome);
    println!("nome e ancora accessibile: {}", nome);
}

Pattern: Conteggio delle Occorrenze

Uno dei pattern piu comuni con le HashMap e il conteggio delle occorrenze, ad esempio per contare la frequenza delle lettere in una stringa:

use std::collections::HashMap;

fn conta_caratteri(testo: &str) -> HashMap<char, usize> {
    let mut frequenze = HashMap::new();
    for c in testo.chars() {
        if !c.is_whitespace() {
            *frequenze.entry(c.to_lowercase().next().unwrap()).or_insert(0) += 1;
        }
    }
    frequenze
}

fn main() {
    let testo = "Ciao Mondo Rust";
    let freq = conta_caratteri(testo);

    // Ordinare per frequenza decrescente
    let mut coppie: Vec<_> = freq.iter().collect();
    coppie.sort_by(|a, b| b.1.cmp(a.1));

    for (carattere, conteggio) in coppie {
        println!("'{}': {}", carattere, conteggio);
    }
}

Aggiornamento Condizionato dei Valori

La Entry API consente anche aggiornamenti condizionati piu sofisticati con and_modify:

use std::collections::HashMap;

fn main() {
    let mut inventario: HashMap<&str, (i32, f64)> = HashMap::new();

    // (quantita, prezzo)
    inventario.insert("mela", (10, 0.50));

    // Aggiorna la quantita se esiste, altrimenti crea la voce
    inventario
        .entry("mela")
        .and_modify(|(qty, _)| *qty += 5)
        .or_insert((1, 0.50));

    inventario
        .entry("pera")
        .and_modify(|(qty, _)| *qty += 5)
        .or_insert((1, 0.75));

    println!("{:?}", inventario);
    // {"mela": (15, 0.5), "pera": (1, 0.75)}
}

Conclusione

La HashMap e una struttura dati fondamentale in Rust per gestire associazioni chiave-valore. La Entry API la distingue da strutture analoghe in altri linguaggi, offrendo un modo elegante e sicuro per gestire inserimenti condizionati. Ricorda che l’ordine di iterazione non e garantito: se hai bisogno di ordine, considera BTreeMap dalla stessa collezione std::collections.