Le strutture dati sono necessarie in qualsiasi forma di programmazione, compresa quella in Rust. Dopo le variabili costituiscono il modo per poter organizzare le informazioni sia internamente sia tra di loro, permettendo di costruire delle vere e proprie reti da analizzare. Ne abbiamo già incontrate tre fondamentali in questa guida: array, tuple e vettori.
Queste rispondono perfettamente ad alcune esigenze ma per poter realizzare un programma completo ce ne servono altre. Le strutture dati non sono fatte solo di dati ma anche di politiche di gestione ovvero le modalità in cui i dati possono al loro interno essere letti e scritti. In base a queste, sapremo quale sarà la struttura dati giusta da impiegare in un determinato contesto.
Politiche di gestione in Rust
Le politiche di gestione principali sono tre:
- accesso sequenziale
Questa esigenza è soddisfatta dalle strutture già citate ovvero array, vettori e tuple; - accesso chiave/valore
- valori singoli senza duplicati
set
In questa lezione ci occuperemo di imparare ulteriori strutture dati oltre a quelle già viste in modo da avere un ventaglio di scelte completo per affrontare ogni casistica che si possa presentare nei nostri programmi. In particolare, ci concentreremo sul secondo e terzo approccio dell'elenco precedente: chiave/valore e set.
HashMap: gestione dei dati chiave/valore
Il modo più immediato per lavorare con l'accesso chiave valore consiste nell'uso di una HashMap
importabile con:
use std::collections::HashMap;
accedendo alle collections della libreria standard.
Come primo esperimento si può iniziare a creare una HashMap
ed inserirvi alcuni elementi tenendo ben presente che andremo ad immettere sempre una coppia ovvero un valore accompagnato da una chiave. Qui supporremo di registrare allievi con i loro voti: la chiave sarà una stringa contenente cognome e nome dell'allievo ed il valore un numero in virgola per rappresentare il voto:
let mut allievi: HashMap<String, f32> = HashMap::new();
allievi.insert(String::from("Rossi Enzo"), 6.5);
allievi.insert(String::from("Bianchi Ilenia"), 8.0);
println!("HashMap = {:?}", allievi);
Abbiamo creato una struttura mutabile e con il metodo insert
vi abbiamo inserito una coppia alla volta. L'output che si ottiene è il seguente:
HashMap = {"Rossi Enzo": 6.5, "Bianchi Ilenia": 8.0}
L'operazione di lettura richiederà la presentazione di una chiave per ottenere, in risposta, il valore corrispondente. Ad esempio, che voto ha preso Ilenia? Chiediamolo alla HashMap:
println!("Voti di Ilenia = {:?}", allievi.get("Bianchi Ilenia"));
Avremo come risposta:
Voti di Ilenia = Some(8.0)
Notiamo che nella stampa "diretta" che si è ottenuta è stato presentato il valore come Some
in quanto non vi è certezza della presenza della chiave richiesta. In alternativa, in caso di chiave non esistente, avremmo ottenuto - come abbiamo già imparato - un None
.
Altre funzioni per la manipolazione in Rust
Esistono inoltre altre funzioni per la manipolazione di questi costrutti che avremo modo di sperimentare nelle prossime esercitazioni. Tra questi:
remove
rimozionelen
numerokeys
elenco delle chiavivalues
elenco dei valori
Set: collezioni senza duplicati
Spesso - non stiamo assolutamente parlando di un caso di nicchia - è fondamentale collezionare elementi senza duplicati. Se ad esempio stiamo cercando URL di pagine web e vogliamo inserirle in maniera unica all'interno di una struttura dati, con i vettori dovremmo, ad ogni inserimento, verificare che l'elemento non sia già presente. In tali casi, avere una struttura dati che non ammette duplicati è fondamentale perché, alla seconda immissione del medesimo valore, questo verrebbe ignorato senza bisogno di alcun controllo da parte nostra con un bel ritorno in termini di produttività ed efficienza. Impariamo ad usare questi costrutti in Rust con l'HashSet
:
use std::collections::HashSet;
fn main() {
let mut frutti: HashSet<&str> = HashSet::new();
frutti.insert("banana");
frutti.insert("mela");
frutti.insert("pera");
frutti.insert("mela");
frutti.insert("pesca");
frutti.insert("banana");
frutti.insert("mela");
println!("Frutti presenti = {:?}", frutti);
if frutti.contains("mela") {
println!("Abbiamo la mela")
}
else {
println!("La mela manca")
}
}
L'esempio ricalca quello precedente sulle "mappe" prevedendo la creazione di un HashSet
ed inserendovi degli elementi. Come si vede, abbiamo provato ad inserire più termini ripetuti (mela e banana) che sono però stati accettati una sola volta infatti l'output del primo println
equivale a Frutti presenti = {"banana", "pera", "mela", "pesca"}. Il blocco if
che segue mostra come poter verificare che un valore sia presente: in questo caso, la mela c'è!
Anche i set sono forniti di molti metodi che possono essere sfruttati per le varie operazioni. Al momento, possiamo citare come principali strumenti utili:
remove
rimozioneunion
unione di insiemiintersection
intersezione di insiemidifference
differenza di insiemi
Anche in questo caso rimandiamo ulteriori approfondimenti ai prossimi esercizi che faremo insieme.