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 += n
quanti++
}
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 (+= a ogni giro), quanti è il contatore (++ 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.
| 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].
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".
split(" ") taglia una stringa a ogni spazio e restituisce la lista delle parole:
"ciao mondo ciao".split(" ") dà [ciao, mondo, ciao]. Lo vedrai
spiegato per bene nel capitolo 8: qui serve solo per contare
le parole di una frase, come prima contavi le lettere di una parola.
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.
mappa[k] = (mappa[k] ?: 0) + 1 è la forma più compatta e funziona sempre.
getOrPut(k) { 0 } è utile quando il valore iniziale è più complicato
(per esempio una lista vuota: mappa.getOrPut(k) { mutableListOf() }.add(x)).
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 Mappe, liste e condizioni insieme: il dizionario con menu
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. Quale riga lo corregge?
fun main() {
val m = mapOf("a" to 1)
m["b"] = 2
}importNon manca nessun import: il problema è che quella mappa è immutabile.mapOf con mutableMapOfEsatto: solo mutableMapOf permette di aggiungere o cambiare voci.m.set("b", 2)set esiste solo sulle mappe modificabili: è il metodo che m[k] = v chiama sotto il cofano. Su una mappa mapOf nessuna scrittura funziona: serve mutableMapOf.Le stesse regole delle liste: mapOf per una mappa che non cambia, mutableMapOf quando 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 volte una coppia nome/numero (sei righe di input in tutto: nome, numero, nome, numero, nome, numero)
e riempi una mappa. Alla fine leggi un ultimo nome e stampa il suo numero, oppure "Contatto non trovato".
Stampa anche tutti i contatti in ordine di inserimento con il formato nome → numero.
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: prima il nome (chiave), poi il numero (valore). L'ordine dei dati di input deve rispettare l'ordine delle
readlnOrNull(). 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 mappa completa, il numero di lettere diverse e la lettera che compare più spesso.
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 e il prodotto più abbondante.
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