Array associativi: le mappe chiave-valore
In un array l'elemento si trova con un numero (l'indice). In una mappa si trova con una chiave: un nome, una parola, un codice. Servono per le rubriche, i dizionari, i conteggi e tutte le volte che "il valore giusto" dipende da un'etichetta.
- creare una mappa:
mutableMapOf("Anna" to 30)emapOf(...); - leggere e scrivere:
mappa["chiave"],mappa["chiave"] = valore,put,get; - controllare e togliere:
containsKey,remove,size; - scorrere una mappa con
for ((chiave, valore) in mappa),keys,values; - lo schema del conteggio:
m[parola] = (m[parola] ?: 0) + 1egetOrPut.
Ripasso lampo: array e liste
Le mappe sono collezioni come le liste: ripasso del capitolo 6. E prima ancora, un salto indietro ai cicli e agli accumulatori del capitolo 5.
Che cosa stampa questo programma?
fun main() {
val a = arrayOf(10, 20, 30)
for (i in a.indices) print("${a[i]} ")
println()
println(a.size - 1)
}10 20 30 e poi 3Attenzione: a.size - 1 è 2, cioè l'indice dell'ultimo elemento.10 20 30 e poi 2Esatto: indices va da 0 a size - 1, e l'ultimo indice è appunto 2.a.indices non esisteEsiste: è la proprietà che ti dà tutti gli indici validi di un array o di una lista.indices dà tutti gli indici validi, da 0 a size - 1. size è quanti elementi ci sono: non è l'ultimo indice, è uno in più.
Che cosa stampa questo programma?
fun main() {
val numeri = listOf(4, 7, 2, 9)
var somma = 0
var quanti = 0
for (n in numeri) {
somma = somma + n
quanti = quanti + 1
}
println("$somma $quanti")
}22 4Esatto: 4 + 7 + 2 + 9 = 22 e il ciclo fa quattro giri, quindi quanti arriva a 4.22 3Attenzione: il contatore parte da 0 e cresce a ogni giro, anche all'ultimo. I giri sono quattro.21 4No: somma parte da 0, quindi il totale è 22. Se partisse da -1 sarebbe 21: è il valore iniziale dell'accumulatore a decidere il risultato.Due variabili, due ruoli: somma è l'accumulatore (somma = somma + n a ogni giro), quanti è il contatore (quanti = quanti + 1 a ogni giro). Entrambe partono da 0 e crescono dentro al ciclo: cambiare il valore iniziale cambia il risultato, non solo l'ultimo passaggio.
7.1 Che cos'è una mappa
Una mappa collega una chiave a un valore. Si legge come una tabella a due colonne:
a sinistra le chiavi (uniche), a destra i valori. In altri libri e linguaggi la stessa struttura si chiama
array associativo o dizionario: sono nomi diversi per lo stesso strumento — qui useremo
sempre i nomi di Kotlin, Map e mutableMapOf.
| Chiave (String) | Valore (Int) |
|---|---|
| "Anna" | 30 |
| "Luca" | 25 |
"Anna" to "333-1112233" crea una coppia chiave-valore.
Nella creazione della mappa le coppie si separano con la virgola.
7.2 Leggere e scrivere
Il valore si legge con le parentesi quadre, come per gli array, ma dentro ci va la chiave.
Per aggiungere o aggiornare una voce si usa la stessa scrittura seguita da =.
eta["Anna"]trova la chiave e dà30.eta["Sara"]non trova niente e dànull: una chiave assente non è un errore, è un valore mancante.- La riga
eta["Sara"] = 19aggiunge una voce nuova: la mappa passa da 2 a 3 elementi. eta.keysstampa l'insieme delle chiavi:[Anna, Luca, Sara].
Come per le liste, una mappa può nascere vuota e riempirsi mentre il programma
gira, con i dati di chi lo usa: si dichiara mutableMapOf<String, Int>() e a
ogni lettura si aggiunge una voce con mappa[nome] = valore. Dopo, cercare dentro
la mappa è il suo lavoro migliore: la chiave trova il valore in un colpo solo, senza scorrere
le coppie una per una.
eta.containsKey(cercato) risponde true o false: è la
strada giusta quando le due risposte sono due messaggi diversi, come nell'esempio. Se invece
ti serve solo il valore, puoi leggere direttamente eta[cercato] ?: 0: se la
chiave manca, al posto di null usi il valore di riserva. E se registri due
persone con lo stesso nome? La seconda età sostituisce la prima: in una
mappa le chiavi sono tutte diverse, una voce per ciascuna.
La mappa prezzi non contiene la chiave "te". Che cosa fa prezzi["te"]?
nullEsatto: una chiave assente non è un errore, è un valore mancante da gestire con ?: o con containsKey.null come se ci fosse un valore, per esempio chiamandoci un metodo sopra.0Kotlin non inventa valori: ti dà null, e il valore di riserva lo scegli tu con ?:.Il modo più comodo: prezzi["te"] ?: 0, che si legge "il prezzo del tè, oppure zero se non c'è".
prezzi["caffe"] restituisce null, non un errore. Per calcolare qualcosa
su quel valore usa ?: (valore di riserva) o controlla prima con containsKey.
Se invece chiami un metodo su un valore null, il programma si ferma.
7.3 Scorrere una mappa
Il ciclo più usato è for ((chiave, valore) in mappa): le due variabili prendono
ogni volta la coppia chiave-valore. In alternativa ci sono keys, values e
entries.
Se il metodo non ti serve, fai lo stesso lavoro con un ciclo e un if:
for ((p, q) in magazzino) if (q < 20) println(p). Il manuale ti mostra i metodi extra,
ma i cicli bastano sempre.
7.4 Lo schema del conteggio
La mappa è perfetta per contare le occorrenze: la chiave è quello che stai contando,
il valore è il contatore. La scrittura (mappa[c] ?: 0) + 1 si legge
"prendi il valore attuale; se non c'è ancora, parti da zero; poi aggiungi uno".
Finora hai contato le lettere di una parola: for (c in parola).
Per contare le parole di una frase ti serve split(" "):
taglia il testo a ogni spazio.
"ciao mondo ciao".split(" ") dà tre pezzi: ciao, mondo, ciao.
Il capitolo 8 lo riprende per intero. Qui usalo solo per questo schema.
split(" ")spezza la frase in tre parole:ciao,mondo,ciao.- La prima volta che vede
ciaola mappa non ha quella chiave:conteggio["ciao"] ?: 0dà0, poi il conteggio diventa1. - La seconda volta la chiave esiste già e vale
1, quindi diventa2:conteggio["ciao"]è2. - Nella mappa ci sono due chiavi distinte,
ciaoemondo:sizeè2.
La forma che ti basta: mappa[k] = (mappa[k] ?: 0) + 1.
getOrPut(k) { 0 } fa lo stesso in due passi.
L'esempio 9 (lista di voti per studente) è un extra: puoi saltarlo alla prima lettura.
Le mappe di questo manuale, quando scorri con for ((k, v) in mappa), presentano
le voci nell'ordine in cui le hai aggiunte: per questo il conteggio delle
lettere sopra parte da p, poi r, e così via. La ricerca del massimo
con lo schema "parti dal primo come riferimento" ne trae vantaggio: se due chiavi hanno lo
stesso valore, vince la prima incontrata. La scelta di questo ordine non è
scontata per una mappa: in generale una mappa garantisce di trovare il valore di una chiave,
non in che ordine presentarle — quando ti serve un ordine preciso, ordina tu le chiavi con
keys.sorted().
7.5 Cercare una parola nel dizionario
Sai già leggere una voce con mappa["chiave"].
Ora unisci mappa e if: se la parola c'è, traduci; se manca, dillo.
trim() toglie gli spazi ai bordi del testo e lowercase() trasforma
le maiuscole in minuscole: così la parola "CASA" e "casa" diventano la stessa richiesta.
Li vedrai spiegati per bene nel capitolo 8: qui sono solo uno
strumento per rendere la ricerca nel dizionario più comoda.
Questo programma dà errore. Perché?
fun main() {
val m = mapOf("a" to 1)
m["b"] = 2
}"b" già scritta nella mappaNo: il problema non è la chiave, è che questa mappa non si può cambiare.mapOf non si possono aggiungere voci: serve mutableMapOfEsatto: mapOf è fissa. Per scrivere m["b"] = 2 la mappa deve essere mutableMapOf."a" va benissimo.Come per le liste: mapOf se non cambia, mutableMapOf se devi aggiungere o aggiornare voci.
7.6 Riepilogo
| Scrivi | Significato |
|---|---|
val m = mutableMapOf("a" to 1, "b" to 2) | mappa modificabile con due voci |
val m = mapOf("a" to 1) | mappa non modificabile |
mutableMapOf<String, Int>() | mappa vuota: chiavi String, valori Int |
m["a"] / m.get("a") | legge il valore (o null) |
m["a"] = 5 / m.put("a", 5) | aggiunge o aggiorna |
m.containsKey("a"), m.remove("a") | controlla se c'è, toglie la voce |
m.size, m.keys, m.values | quante voci, tutte le chiavi, tutti i valori |
for ((k, v) in m) | scorre chiavi e valori |
m.getOrDefault(k, 0), m[k] ?: 0 | valore di riserva se la chiave non c'è |
- La scrittura si scrive
m[k] = v(oppurem.put(k, v)) e funziona solo su unamutableMapOf: su una mappamapOfnon c'è verso, è immutabile. - Chiamare un metodo su un valore letto e assente (
null) fa fermare il programma: usa?:. - Una mappa creata con
mapOfnon si modifica: servemutableMapOf. - Le chiavi sono uniche: scrivere due volte la stessa chiave sostituisce il valore, non aggiunge una voce.
7.7 Esercizi guidati
Leggi tre coppie nome / numero (6 righe). Poi leggi un settimo nome da cercare.
Stampa in questo ordine:
1. il numero di quel nome, oppure la riga Contatto non trovato;
2. poi una riga vuota, il titolo --- Rubrica completa (3 contatti) ---
e tutti i contatti, uno per riga, nel formato nome → numero.
Con ricerca Anna la prima riga è Anna: 333-1112233.
Per la ricerca usa containsKey (oppure rubrica[ricerca] ?: "Contatto non trovato").
Per stampare tutti i contatti usa for ((nome, numero) in rubrica).
- Il ciclo legge due righe per volta con
readln(): prima il nome (chiave), poi il numero (valore). rubrica[nome] = numerofunziona sia per aggiungere un contatto nuovo sia per aggiornare uno esistente.containsKeyevita di stamparenullquando il contatto non esiste.- Il ciclo stampa le voci nell'ordine di inserimento: le mappe del manuale conservano quest'ordine.
Leggi una parola e conta quante volte compare ogni lettera usando una mappa Char → Int.
Alla fine stampa: la parola, la mappa completa, il numero di lettere diverse e la lettera che compare più spesso.
In parità tieni la prima lettera che raggiunge il massimo.
Con programmazione l'ultima riga è
La lettera più frequente è 'r' (2 volte) e le lettere diverse sono 10.
Per trovare la lettera più frequente puoi scorrere la mappa con un ciclo e confrontare i valori, partendo dalla prima voce come riferimento.
- La mappa usa
Charcome chiave eIntcome valore: ogni lettera nuova crea automaticamente una voce. (conteggio[c] ?: 0) + 1gestisce insieme il primo incontro della lettera (valore assente → 0) e gli incontri successivi.- Il secondo ciclo cerca il valore massimo scorrendo la mappa: lo schema "parti dal primo come riferimento" funziona anche qui, con le due variabili
massimoeletteraPiuFrequente. - Se due lettere comparissero lo stesso numero di volte, vince la prima incontrata nel ciclo: è un comportamento accettabile per questo esercizio.
Leggi quattro coppie prodotto/quantità. Le quantità dello stesso prodotto si sommano
(per le matite arriveranno due volte). Poi:
stampa l'inventario, il totale dei pezzi, i prodotti con meno di 20 pezzi
(subito sotto, la riga -> sotto scorta!) e il prodotto più abbondante.
Con i dati di prova le matite diventano 50, il totale 73 e il più abbondante è matite.
Per sommare: inventario[prodotto] = (inventario[prodotto] ?: 0) + quantita.
Per i prodotti sotto scorta scorri la mappa con un ciclo e un if.
- La somma
(inventario[prodotto] ?: 0) + quantitafunziona sia alla prima comparsa del prodotto sia alle successive: le matite finiscono con 50 pezzi. - Un solo ciclo sulla mappa calcola tre cose insieme: la stampa, il totale e il massimo. Non serve ripetere lo scorrimento.
massimoparte da -1 (e non da 0) per funzionare anche con quantità tutte negative o nulle.- Stampare "sotto scorta" dentro al ciclo evita di raccogliere prima l'elenco e stamparlo dopo.
Il programma deve contare quante volte compare ogni parola della frase ciao mondo ciao, ma non funziona. Alla fine deve stampare ciao: 2 e mondo: 1.
Una mappa creata con mapOf non si modifica: serve mutableMapOf. E la prima volta che incontri una parola la chiave non esiste ancora: il suo valore va preso con ?:. split(" ") è l'assaggio dal capitolo 8 che hai visto nella sezione 7.4: divide la frase nelle singole parole.
mapOfcrea una mappa immutabile: l'assegnazioneconteggio[parola] = ...è l'errore che ferma il programma.- Anche con
mutableMapOfresta un problema: la prima volta che compare una parola,conteggio[parola]ènull. (conteggio[parola] ?: 0) + 1si legge "il valore attuale, oppure 0 se la chiave non c'è ancora, più uno".- Le chiavi sono uniche: la seconda volta che vede
ciaola mappa non aggiunge una voce, aggiorna quella che c'è già.
mutableMapOf e mapOfmappa["chiave"] e so che cosa succede se mancaremovefor ((chiave, valore) in mappa)(m[k] ?: 0) + 1keys, values e containsKey