Capitolo 7

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.

In questo capitolo impari

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.
Un errore, perché 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
La parola "to"

"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 =.

  1. eta["Anna"] trova la chiave e dà 30.
  2. eta["Sara"] non trova niente e dà null: una chiave assente non è un errore, è un valore mancante.
  3. La riga eta["Sara"] = 19 aggiunge una voce nuova: la mappa passa da 2 a 3 elementi.
  4. eta.keys stampa l'insieme delle chiavi: [Anna, Luca, Sara].

La mappa prezzi non contiene la chiave "te". Che cosa fa prezzi["te"]?

Restituisce nullEsatto: una chiave assente non è un errore, è un valore mancante da gestire con ?: o con containsKey.
Fa fermare il programma con un erroreNo: l'errore arriva solo se usi quel null come se ci fosse un valore, per esempio chiamandoci un metodo sopra.
Restituisce 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'è".

La chiave assente non dà errore: dà null

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.

filterValues non è obbligatorio

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: un assaggio dal capitolo 8

split(" ") taglia una stringa a ogni spazio e restituisce la lista delle parole: "ciao mondo ciao".split(" ")[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.

  1. split(" ") spezza la frase in tre parole: ciao, mondo, ciao.
  2. La prima volta che vede ciao la mappa non ha quella chiave: conteggio["ciao"] ?: 00, poi il conteggio diventa 1.
  3. La seconda volta la chiave esiste già e vale 1, quindi diventa 2: conteggio["ciao"] è 2.
  4. Nella mappa ci sono due chiavi distinte, ciao e mondo: size è 2.
Quale forma scegliere

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)).

L'ordine delle voci

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 e lowercase: un assaggio dal capitolo 8

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
}
Aggiungere importNon manca nessun import: il problema è che quella mappa è immutabile.
Sostituire mapOf con mutableMapOfEsatto: solo mutableMapOf permette di aggiungere o cambiare voci.
Scrivere 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

ScriviSignificato
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.valuesquante voci, tutte le chiavi, tutti i valori
for ((k, v) in m)scorre chiavi e valori
m.getOrDefault(k, 0), m[k] ?: 0valore di riserva se la chiave non c'è
Errori tipici con le mappe

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).

  1. 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().
  2. rubrica[nome] = numero funziona sia per aggiungere un contatto nuovo sia per aggiornare uno esistente.
  3. containsKey evita di stampare null quando il contatto non esiste.
  4. 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.

  1. La mappa usa Char come chiave e Int come valore: ogni lettera nuova crea automaticamente una voce.
  2. (conteggio[c] ?: 0) + 1 gestisce insieme il primo incontro della lettera (valore assente → 0) e gli incontri successivi.
  3. Il secondo ciclo cerca il valore massimo scorrendo la mappa: lo schema "parti dal primo come riferimento" funziona anche qui, con le due variabili massimo e letteraPiuFrequente.
  4. 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.

  1. La somma (inventario[prodotto] ?: 0) + quantita funziona sia alla prima comparsa del prodotto sia alle successive: le matite finiscono con 50 pezzi.
  2. Un solo ciclo sulla mappa calcola tre cose insieme: la stampa, il totale e il massimo. Non serve ripetere lo scorrimento.
  3. massimo parte da -1 (e non da 0) per funzionare anche con quantità tutte negative o nulle.
  4. 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.

  1. mapOf crea una mappa immutabile: l'assegnazione conteggio[parola] = ... è l'errore che ferma il programma.
  2. Anche con mutableMapOf resta un problema: la prima volta che compare una parola, conteggio[parola] è null.
  3. (conteggio[parola] ?: 0) + 1 si legge "il valore attuale, oppure 0 se la chiave non c'è ancora, più uno".
  4. Le chiavi sono uniche: la seconda volta che vede ciao la mappa non aggiunge una voce, aggiorna quella che c'è già.
  • so creare una mappa con mutableMapOf e mapOf
  • so leggere una voce con mappa["chiave"] e so che cosa succede se manca
  • so aggiungere e aggiornare voci e so togliere con remove
  • so scorrere una mappa con for ((chiave, valore) in mappa)
  • so usare lo schema del conteggio (m[k] ?: 0) + 1
  • so usare keys, values e containsKey
  • so leggere un messaggio di errore e correggere il programma da solo