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.