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.
- gli array a dimensione fissa:
intArrayOf(...),arrayOf(...),IntArray(n) { ... }; - le liste:
listOf(...)per i valori fissi emutableListOf(...)per quelle che crescono e si accorciano; - gli indici (da 0!) e la proprietà
size; - i metodi utili:
add,remove,sum,max,sorted,filter,map,joinToString.
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.
sizeè3: l'array contiene tre elementi.voti[0]è il primo elemento,7: gli indici partono da 0.- Sostituendo
voti[0]con10l'array diventa10, 8, 6. La dimensione, invece, non cambia: resta 3. voti.last()è l'ultimo elemento, cioèvoti[voti.size - 1]:6.
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])
}voti[2], perché si conta da 0.size - 1.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.
intArrayOf(1, 2, 3)eIntArray(3)→ numeri interi;doubleArrayOf(1.5, 2.5)eDoubleArray(3)→ numeri decimali;arrayOf("a", "b")eArray(3) { ... }→ testi, booleani o oggetti.
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 10:
lo vedrai nel capitolo 11.
6.2 Le liste: listOf e mutableListOf
Una lista è una collezione come l'array, ma con due differenze importanti:
si crea con listOf(...) o mutableListOf(...), e la seconda può
crescere e accorciarsi — add e remove funzionano solo
su di lei. Anche una lista può partire vuota: mutableListOf<String>().
Cominciamo dalla lista che cambia.
- Si parte da due elementi,
addne aggiunge uno (pane, latte, uova). remove("latte")toglie l'elemento con quel valore: restanopaneeuova.println(spesa)stampa la lista fra parentesi quadre con le virgole:[pane, uova].sizeora è2.
listOf(...) crea una lista non modificabile: i valori si decidono
quando scrivi il programma e da lì in poi si leggono soltanto — indici, size,
for, sum(), filter, sorted e tutti gli altri
metodi della tabella del §6.3. Se provi a modificarla il manuale ti avvisa:
"Questa lista è immutabile (creata con listOf). Per modificarla usa
mutableListOf(...)". Vale per add, remove,
clear, sort e anche per giorni[0] = "dom".
mutableListOf(...) è invece la lista che cresce e si accorcia:
serve quando il numero di elementi lo decidono i dati o chi usa il programma — i voti letti
finché l'utente non scrive -1, le voci di una spesa che si aggiungono una alla volta.
Quindi la regola è: se la lista non deve cambiare, usa listOf —
dice chiaramente che quei valori sono fissi, e chi legge il programma lo sa subito. Se poi ti
serve poter aggiungere qualcosa, cambi listOf in mutableListOf e il
resto del programma resta uguale.
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)
}l.add(4)Funziona, ma perdi l'elemento che volevi aggiungere: non è la correzione giusta se ti serve una lista che cresce.mutableListOf(1, 2, 3)Esatto: listOf crea una lista immutabile, mutableListOf una che si può modificare con add e remove.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.
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. Valgono per tutte le liste, quelle di
listOf comprese: l'unica differenza fra listOf e
mutableListOf è che la prima non si può modificare, quindi le righe della tabella
che aggiungono, tolgono o riordinano sul posto valgono solo per mutableListOf.
| Metodo | Cosa fa |
|---|---|
lista.size | quanti elementi contiene |
lista[i] | legge l'elemento in posizione i |
lista[i] = x | cambia 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 (non modificabile, come quelle di listOf) |
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 |
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.
| Metodo | Cosa 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 |
toMutableList() | una copia modificabile di una lista letta: serve per aggiungere a un risultato di filter o sorted |
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.
filter, map, sorted, distinct e
reversed non toccano la lista di partenza: restituiscono una lista
nuova, dello stesso tipo di quelle create con listOf, quindi non
modificabile. È il comportamento giusto — voti resta com'era e il risultato è un
elenco a sé — ma è anche il motivo per cui voti.filter { ... }.add(10) si ferma con
l'errore "questa lista è immutabile". Se ti serve poter aggiungere a quel risultato, chiedine
una copia modificabile con toMutableList(), oppure costruisci una
mutableListOf e riempila con un ciclo.
{ 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.
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?".
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, listOf o mutableListOf?
| Aspetto | intArrayOf, arrayOf, IntArray | listOf | mutableListOf |
|---|---|---|---|
| Dimensione | fissa: decisa alla creazione | fissa: decisa alla creazione | variabile: cresce e diminuisce |
| Aggiungere e togliere | no (errore) | no (errore) | si: add, remove, clear |
| Cambiare un elemento | si: a[0] = 5 | no (errore) | si: l[0] = 5 |
Metodi delle collezioni (sum, filter, sorted…) | si | si | si |
| Quando usarla | quantità nota e tipi semplici (Int, Double…) | valori decisi quando scrivi il programma | quantità che dipende dai dati o dall'utente |
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. Se invece i valori sono quelli che hai
scritto nel codice (i giorni della settimana, le materie, le tariffe), listOf è la
scelta più chiara: dice subito che quell'elenco non cambia. L'array va bene quando la quantità è
certa e i valori sono numeri semplici, per esempio le temperature dei 7 giorni.
6.5 Riepilogo
| Scrivi | Cosa 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] = 10 | legge e cambia l'elemento in posizione i (si parte da 0) |
voti.size, voti.indices | quanti 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, con i valori decisi nel programma: add e lista[0] = x darebbero errore |
lista.toMutableList() | una copia modificabile di una lista letta (per esempio del risultato di filter) |
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().
IntArray(5)crea un array di cinque zeri: le posizioni esistono già, basta riempirle.for (i in 0 until 5)visita gli indici 0, 1, 2, 3, 4: esattamente le posizioni valide dell'array.average()restituisce unDoubleanche partendo da interi: la media mantiene i decimali.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").
mutableListOf<String>()crea una lista vuota di testi: si possono aggiungere elementi quando serve.add("pane")mette in fondo;add(1, "burro")inserisce nella posizione 1 e sposta tutto il resto.remove("latte")toglie dall'elenco, non dal frigorifero: la lista resta con 4 elementi.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.
- La lista parte vuota e cresce con
add: è il caso tipico in cui servemutableListOf. - La media si calcola prima del conteggio, perché serve come termine di confronto.
- I due
ifsono separati: un voto uguale alla media non entra in nessuno dei due contatori. 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.
- Il codice compila: l'errore è logico. Con
it > 6il voto 6 viene scartato dal filtro. - Con
it >= 6il 6 è incluso: da 3 sufficienti si passa a 5, che è il risultato corretto. - Gli errori di un carattere (
>contro>=) non danno messaggi: si trovano solo confrontando l'output atteso con quello reale. - La stessa distinzione vale per
count,anyeall: controlla sempre se l'estremo è compreso.
Scrivi da zero un programma che registra i voti di una verifica. Il programma legge, in quest'ordine:
- una prima riga con il numero di voti da inserire (per esempio
4); - poi una riga per ogni voto. Se una riga non è un numero intero, considerala
0(usatoIntOrNull() ?: 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.
- La lista è la scelta giusta perché il numero di voti non è noto in anticipo: con un array dovresti decidere la dimensione prima di leggere.
1..quanticonquantiuguale a 0 non fa nessun giro: il ciclo si occupa da solo del caso limite.toIntOrNull() ?: 0trasforma una riga non numerica in0, così il programma non si ferma su un dato sporco.- Il controllo
voti.isEmpty()è obbligatorio prima dimax()eaverage(): su una lista vuota non sanno che cosa restituire. 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.
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".- La forma corretta è
0 until voti.size, che dà 0, 1, 2. - Nel testo stamperei l'indice:
${i + 1}serve a mostrare "Voto 1" per l'elemento di indice 0. 6 + 7 + 8 = 21, e21 / 3fra interi fa7: per ottenere7.0serve almeno unDouble, per esempiovoti.sum().toDouble().
intArrayOf, doubleArrayOf, arrayOf e IntArray(n) { }size - 1listOf e mutableListOf e so spiegare la differenzaadd e removesum, max, sorted, filter, map, joinToString