Capitolo 6

Array e liste: tanti valori in una sola variabile

Finora ogni variabile conteneva un solo valore. Con gli array e le liste puoi conservare molti valori insieme (i voti di una classe, i nomi degli studenti, le temperature della settimana) e lavorarci con cicli e metodi pronti.

In questo capitolo impari

Ripasso lampo: cicli e accumulatori

Array e liste si usano quasi sempre dentro un ciclo: ripasso del capitolo 5.

Che cosa stampa questo programma?

fun main() {
    for (i in 1..5) if (i % 2 == 0) print("$i ")
}
1 2 3 4 5No: la if filtra, non stampa tutti i valori.
2 4Esatto: i % 2 == 0 è vero solo per i numeri pari.
2 4 con una riga vuota dopoIl testo è giusto, ma print non manda a capo: nessuna riga vuota in più.

Una if dentro a un ciclo non cambia quante volte il ciclo si ripete: decide soltanto che cosa succede in quel giro.

Che cosa stampa questo programma?

fun main() {
    var somma = 0
    for (i in 1..4) somma += i
    println(somma)
}
10Esatto: 1 + 2 + 3 + 4 = 10. È lo schema dell'accumulatore: parti da 0 e sommi a ogni giro.
4No: somma += i aggiunge ogni valore, non tiene solo l'ultimo.
0La variabile somma cambia dentro al ciclo.

L'accumulatore parte da zero e cresce a ogni giro. Le due cose da controllare sono il valore iniziale e il punto in cui si somma: se lo sposti fuori dal ciclo, il conto non funziona più.

6.1 Array a dimensione fissa

Un array si crea elencando i valori fra parentesi tonde. La sua dimensione resta quella: non puoi aggiungere elementi, ma puoi leggere e sostituire quelli che ci sono. Ogni elemento ha un indice che parte da 0.

  1. size è 3: l'array contiene tre elementi.
  2. voti[0] è il primo elemento, 7: gli indici partono da 0.
  3. Sostituendo voti[0] con 10 l'array diventa 10, 8, 6. La dimensione, invece, non cambia: resta 3.
  4. voti.last() è l'ultimo elemento, cioè voti[voti.size - 1]: 6.
Attenzione all'indice!

Con 4 elementi gli indici validi sono 0, 1, 2, 3. voti[4] fa apparire l'errore "Indice 4 fuori dai limiti". L'ultimo indice è sempre size - 1.

Esistono anche costruttori che creano array vuoti da riempire: IntArray(5) crea cinque zeri, mentre IntArray(5) { it * it } usa una lambda per calcolare i valori (it è l'indice di ogni posizione). Attenzione a questo primo significato di it: qui è la posizione, perché il costruttore deve sapere che valore mettere in ogni casella. Più avanti, nei metodi di lista, it sarà invece l'elemento (vedi il paragrafo dopo la sezione 6.3).

Un array ha 3 elementi. Che cosa succede con voti[3]?

fun main() {
    val voti = intArrayOf(7, 8, 6)
    println(voti[3])
}
Stampa il terzo votoAttenzione: il "terzo" elemento è voti[2], perché si conta da 0.
Il programma si ferma con un errore di indice fuori dai limitiEsatto: con 3 elementi gli indici validi sono 0, 1 e 2. L'ultimo è sempre size - 1.
Stampa nullNo: gli errori di indice non danno null, fermano il programma.

Schema da ricordare: l'ultimo indice è lista.size - 1, e la posizione centrale di un ciclo che scorre tutti gli elementi è 0 until lista.size.

Che tipi di array esistono

Un esempio per ciascuno dei tre tipi: cambia soltanto il costruttore. Indici, size, for e i metodi delle collezioni funzionano allo stesso modo.

L'ultimo tipo è il più elastico: con arrayOf si mettono in fila anche gli oggetti, cioè le istanze delle classi del capitolo 9: lo vedrai nel capitolo 10.

6.2 Liste a dimensione variabile

Quando il numero di elementi può cambiare servono le liste: mutableListOf(...) si può modificare (add, remove), listOf(...) no. Una lista può partire vuota: mutableListOf<String>().

  1. Si parte da due elementi, add ne aggiunge uno (pane, latte, uova).
  2. remove("latte") toglie l'elemento con quel valore: restano pane e uova.
  3. println(spesa) stampa la lista fra parentesi quadre con le virgole: [pane, uova]. size ora è 2.

Questo programma si ferma con un errore. Quale correzione lo fa funzionare?

fun main() {
    val l = listOf(1, 2, 3)
    l.add(4)
    println(l)
}
Togliere la riga l.add(4)Funziona, ma perdi l'elemento che volevi aggiungere: non è la correzione giusta se ti serve una lista che cresce.
Usare mutableListOf(1, 2, 3)Esatto: listOf crea una lista immutabile, mutableListOf una che si può modificare con add e remove.
Usare intArrayOf(1, 2, 3)Anche l'array è a dimensione fissa: add non esiste nemmeno.

Regola pratica: se l'elenco deve crescere o accorciarsi, dichiara mutableListOf; se i valori sono sempre quelli, listOf va benissimo.

listOf e mutableListOf

listOf(1, 2, 3) crea una lista immutabile: perfetta quando i valori sono fissi. Se provi a usare add su una lista creata con listOf il manuale ti avvisa: "Questa lista è immutabile... usa mutableListOf". Nel dubbio, usa mutableListOf.

6.3 I metodi che userai di più

Questi sono i metodi che il capitolo ha già usato negli esempi e negli esercizi: sono quelli da sapere adesso.

MetodoCosa fa
lista.sizequanti elementi contiene
lista[i]legge l'elemento in posizione i
lista[i] = xcambia l'elemento in posizione i
lista.add(x), lista.add(i, x)aggiunge in fondo / in posizione i (solo liste modificabili)
lista.remove(x), lista.clear()toglie un valore / svuota la lista
lista.contains(x), lista.indexOf(x)c'è? in che posizione?
lista.isEmpty()è vuota? Utile prima di max() e average()
sum(), average(), max(), min()somma, media, massimo, minimo
sorted(), sortedDescending(), distinct()una nuova lista ordinata, invertita, senza duplicati
sort()ordina la lista stessa (solo liste modificabili)
map { }, filter { }, count { }trasforma / tiene solo alcuni / li conta
any { }, all { }, last()almeno uno / tutti / ultimo
joinToString(", "), contentToString()trasforma la collezione in un testo
Questi li incontrerai più avanti: non serve ricordarli adesso

Esistono e li ritroverai leggendo codice altrui, ma in questo capitolo non li hai ancora usati: consulta la tabella quando ti serve uno di questi, senza impararla a memoria ora.

MetodoCosa fa
lista.get(i), lista.set(i, x)la versione "a metodo" di lista[i] e lista[i] = x
lista.removeAt(i)toglie l'elemento in posizione i
maxOrNull(), minOrNull()massimo e minimo che danno null su una lista vuota (vedi il riquadro qui sotto)
first()primo elemento
reversed()una nuova lista con l'ordine inverso
sortBy { ... }ordina la lista stessa secondo un criterio
forEach { }ripete qualcosa per ogni elemento
take(n), drop(n), subList(a, b)i primi n / tutti tranne i primi n / una parte della lista
Attenzione: su una collezione vuota max(), min() e average() si fermano

max(), min() e average() hanno bisogno di almeno un elemento: se la lista è vuota non sanno che cosa restituire e fermano il programma con un errore. Quando la lista può essere vuota (per esempio i voti letti finché l'utente non scrive -1) usa le varianti maxOrNull() e minOrNull(): al posto del risultato danno null, che gestisci con ?: o con un controllo if. Per la media non esiste la variante OrNull: controlla prima lista.isEmpty(). Se invece sai già che la collezione ha almeno un elemento, max() e min() vanno benissimo.

Come si chiama quel pezzo di codice fra graffe

{ it >= 6 } è una lambda: una piccola funzione senza nome, scritta in linea, che passi a un metodo perché la applichi a ogni elemento. Dentro le graffe puoi usare direttamente la variabile it, che è l'elemento che sta scorrendo in quel momento; con { voto -> voto >= 6 } puoi anche dargli tu un nome. Non serve saperne di più per adesso: le rivedrai in ogni metodo della tabella sopra.

Due significati di it

Qui dentro filter { it >= 6 } il it è ogni elemento della collezione, uno alla volta: il metodo controlla la condizione su ciascuno. Nel costruttore IntArray(6) { it * it }, invece, it era la posizione. Il trucco per non confondersi: chiediti sempre "che cosa sta scorrendo in questo momento?".

Lo schema "primo elemento come riferimento"

Per trovare il massimo (o il minimo) si parte dal primo elemento, non da zero: così funziona anche con numeri tutti negativi. Poi si confronta ogni elemento e si aggiorna il riferimento. Con max() e min() Kotlin fa lo stesso lavoro in una riga, ma sapere scriverlo a mano serve per capire come funziona e per i casi più complessi (per esempio il massimo fra voti con il nome dello studente).

6.4 Array o lista?

AspettoArray (intArrayOf, arrayOf, IntArray)Lista (mutableListOf, listOf)
Dimensionefissa: decisa alla creazionevariabile: cresce e diminuisce
Aggiungere elementino (errore)si, con add
Cambiare un elementosisi (se modificabile)
Quando usarlodati di quantità notadati di quantità variabile
Consiglio pratico

Se non sai in anticipo quanti elementi avrai (per esempio i voti letti finché l'utente non scrive -1), usa mutableListOf: il programma resta semplice. L'array fisso va bene quando la quantità è certa, per esempio le temperature dei 7 giorni.

6.5 Riepilogo

ScriviCosa fa
val voti = intArrayOf(7, 8, 9)array di interi a dimensione fissa, con i valori già dentro
val zeri = IntArray(4), DoubleArray(3)array vuoto riempito di zeri, da riempire dopo
IntArray(6) { it * it }array costruito da una lambda: it è la posizione
voti[1], voti[1] = 10legge e cambia l'elemento in posizione i (si parte da 0)
voti.size, voti.indicesquanti elementi ha / le posizioni da 0 a size - 1
voti.contentToString()l'array come testo da stampare: [7, 8, 9]
val spesa = mutableListOf<String>()lista modificabile (anche vuota): cresce e si accorcia
spesa.add("pane"), spesa.remove("pane")aggiunge in fondo / toglie la prima voce uguale
listOf(1, 2, 3)lista non modificabile: add darebbe errore
spesa.size, spesa.contains("pane")quanti elementi / c'è quel valore?
voti.sum(), max(), min(), average()somma, massimo, minimo, media degli elementi
voti.filter { it >= 6 }, map { it * 2 }tiene solo alcuni elementi / li trasforma
voti.joinToString(", ")mette gli elementi in un testo, separati da virgole
for (x in voti), for (i in voti.indices)scorre i valori / scorre le posizioni

6.6 Esercizi guidati

Leggi 5 voti in un array, poi calcola e stampa: tutti i voti, la media, il voto massimo e il voto minimo, quanti voti sono sufficienti (da 6 in su) e l'elenco dei soli insufficienti.

L'array è già riservato, ma è vuoto: riempilo con un ciclo for (i in 0 until 5) e readln().toInt(), come hai imparato nel capitolo 5. Per i voti insufficienti puoi creare una lista vuota e riempirla nel ciclo, oppure usare voti.filter { it < 6 }. Per la media usa voti.average().

  1. IntArray(5) crea un array di cinque zeri: le posizioni esistono già, basta riempirle.
  2. for (i in 0 until 5) visita gli indici 0, 1, 2, 3, 4: esattamente le posizioni valide dell'array.
  3. average() restituisce un Double anche partendo da interi: la media mantiene i decimali.
  4. filter { it < 6 } crea una nuova lista con i soli voti insufficienti; it è ogni voto, preso uno alla volta.

Crea una lista della spesa vuota e aggiungi questi prodotti nell'ordine: pane, latte, mele, pasta. Poi: inserisci "burro" in seconda posizione, togli "latte", ordina la lista alfabeticamente, stampala con joinToString e mostra quanti prodotti restano.

Per ordinare la lista stessa usa spesa.sort() (funziona sui testi: ordine alfabetico). Per inserire in seconda posizione: spesa.add(1, "burro").

  1. mutableListOf<String>() crea una lista vuota di testi: si possono aggiungere elementi quando serve.
  2. add("pane") mette in fondo; add(1, "burro") inserisce nella posizione 1 e sposta tutto il resto.
  3. remove("latte") toglie dall'elenco, non dal frigorifero: la lista resta con 4 elementi.
  4. sort() ordina la lista stessa (non restituisce una nuova lista): per questo non c'è bisogno di riassegnare nulla.

Leggi 7 voti in una lista modificabile, calcola la media e poi conta quanti voti sono sopra la media e quanti sotto. Stampa i voti ordinati, la media e i due conteggi.

Dopo aver calcolato la media, usa un ciclo (o count { it > media }) per contare i voti sopra e sotto. Attenzione: i voti esattamente uguali alla media non sono né sopra né sotto.

  1. La lista parte vuota e cresce con add: è il caso tipico in cui serve mutableListOf.
  2. La media si calcola prima del conteggio, perché serve come termine di confronto.
  3. I due if sono separati: un voto uguale alla media non entra in nessuno dei due contatori.
  4. voti.sorted() restituisce una copia ordinata: la lista originale conserva l'ordine di inserimento.

Questo programma parte senza errori, ma con i voti 6, 4, 8, 7, 5, 9, 6 dice che i sufficienti sono 3 invece di 5: il 6 non viene contato. Trova l'errore logico nella condizione del filter e correggilo.

La sufficienza parte da 6 compreso: il confronto giusto è it >= 6, non it > 6. Un solo carattere cambia il conteggio.

  1. Il codice compila: l'errore è logico. Con it > 6 il voto 6 viene scartato dal filtro.
  2. Con it >= 6 il 6 è incluso: da 3 sufficienti si passa a 5, che è il risultato corretto.
  3. Gli errori di un carattere (> contro >=) non danno messaggi: si trovano solo confrontando l'output atteso con quello reale.
  4. La stessa distinzione vale per count, any e all: controlla sempre se l'estremo è compreso.

Scrivi da zero un programma che registra i voti di una verifica. Il programma legge, in quest'ordine:

  1. una prima riga con il numero di voti da inserire (per esempio 4);
  2. poi una riga per ogni voto. Se una riga non è un numero intero, considerala 0 (usa toIntOrNull() ?: 0).

Metti i voti in una lista (il numero non è noto in anticipo) e stampa, nell'ordine: i voti raccolti, quanti sono, la loro somma, la media, il voto massimo e quanti sono sufficienti (da 6 in su). Con l'input 4, 7, assente, 9, 6 i voti sono quattro e il terzo vale 0.

Caso limite da gestire: se il numero iniziale è 0 (o la prima riga non è un numero) non leggere nessun voto e stampa Nessun voto inserito; in quel caso non chiamare max() e average(), che su una lista vuota fermano il programma.

Riempi la lista con un ciclo for (i in 1..quanti) e converti ogni riga con toIntOrNull() ?: 0. Le statistiche si fanno con sum(), average(), max() e count { it >= 6 }, ma solo dentro un if (voti.isEmpty()) ... else ...: così il caso della lista vuota è coperto prima di chiedere il massimo o la media.

  1. La lista è la scelta giusta perché il numero di voti non è noto in anticipo: con un array dovresti decidere la dimensione prima di leggere.
  2. 1..quanti con quanti uguale a 0 non fa nessun giro: il ciclo si occupa da solo del caso limite.
  3. toIntOrNull() ?: 0 trasforma una riga non numerica in 0, così il programma non si ferma su un dato sporco.
  4. Il controllo voti.isEmpty() è obbligatorio prima di max() e average(): su una lista vuota non sanno che cosa restituire.
  5. count { it >= 6 } conta i sufficienti senza costruire una seconda lista: il 6 è compreso.

Il programma deve stampare i voti e la loro media, ma si ferma con un errore e poi calcola male. Con i voti 6, 7, 8 la media è 7.0.

Gli indici partono da 0 e l'ultimo è size - 1: per scorrere tutto l'array si usa 0 until voti.size. E la divisione fra due Int taglia i decimali.

  1. for (i in 1..voti.size) produce gli indici 1, 2 e 3: voti[3] non esiste e il programma si ferma con "Indice 3 fuori dai limiti".
  2. La forma corretta è 0 until voti.size, che dà 0, 1, 2.
  3. Nel testo stamperei l'indice: ${i + 1} serve a mostrare "Voto 1" per l'elemento di indice 0.
  4. 6 + 7 + 8 = 21, e 21 / 3 fra interi fa 7: per ottenere 7.0 serve almeno un Double, per esempio voti.sum().toDouble().
  • so creare un array con intArrayOf, doubleArrayOf, arrayOf e IntArray(n) { }
  • so usare gli indici sapendo che partono da 0 e che l'ultimo è size - 1
  • so scegliere fra array e mutableListOf
  • so aggiungere e togliere elementi con add e remove
  • so usare sum, max, sorted, filter, map, joinToString
  • so scrivere a mano la ricerca del massimo partendo dal primo elemento
  • so leggere un messaggio di errore e correggere il programma da solo