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.
- a mettere tanti numeri o testi in un array e a leggerli con gli indici (si parte da 0);
- la differenza fra array,
listOf(elenco fisso) emutableListOf(elenco che cresce); - a scorrere gli elementi con
fore a usareadd,remove,size,sum,average. filteremapli vedi in fondo: servono, ma dopo aver capito indici e liste.
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, e in 1..5 i pari sono 2 e 4.1 3 5Quelli sono i dispari: sarebbe i % 2 != 0.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 = somma + i
println(somma)
}10Esatto: 1 + 2 + 3 + 4 = 10. È lo schema dell'accumulatore: parti da 0 e sommi a ogni giro.4No: somma = 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 è una fila di caselle numerate: si crea elencando i valori fra parentesi
tonde e la sua dimensione resta quella — non puoi aggiungere elementi, ma puoi
leggere e sostituire quelli che ci sono. Ogni casella 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.
intArrayOf(1, 2, 3)→ interi;doubleArrayOf(1.5, 2.5)→ decimali;arrayOf("Roma", "Milano")→ testi.
Il nome della funzione dice che cosa c'è nelle caselle.
Indici, size e for funzionano allo stesso modo. Per ora usa questi tre.
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.Con 3 elementi gli indici validi sono 0, 1 e 2. L'ultimo è sempre size - 1. Per scorrere tutte le caselle: for (i in 0 until voti.size). until esclude size, quindi non chiedi una casella che non esiste.
Gli array possono viaggiare in parallelo: la posizione 0 di ognuno riguarda lo stesso prodotto, la posizione 1 un altro, e così via. In un unico ciclo si riempiono tutti e tre; dopo, basta trovare la posizione di un nome per leggere subito sconto e prezzo con lo stesso indice.
Quando i valori seguono una regola — i quadrati, i doppi, le potenze — si può dire a Kotlin di
calcolarli invece di scriverli: IntArray(6) { it * it } crea sei caselle con i
quadrati da 0 a 5. Che cosa significhi quel pezzo fra graffe è spiegato nel §6.3, quando
servirà davvero: per ora basta sapere che esiste.
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.
listOf è il modo più diretto di creare una lista: gli elementi si scrivono fra le
parentesi, uno dopo l'altro — listOf("pane", "latte", "mele") — e da quel momento
la lista resta com'è: niente add, niente remove. È l'analogo di
intArrayOf e arrayOf per gli array. Con mutableListOf si
può fare la stessa cosa — mutableListOf("pane", "latte") — oppure partire da una
lista vuota, mutableListOf<String>(), e riempirla un elemento alla volta.
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.
Negli esempi finora gli elementi erano scritti nel codice. Di solito però arrivano da chi usa il programma: la lista nasce vuota e cresce a ogni lettura.
Le liste modificabili possono anche viaggiare in parallelo: nello stesso giro del ciclo si aggiunge un elemento a ciascuna, così la posizione 0 di tutte riguarda lo stesso elemento — un libro e il suo prezzo, uno studente e il suo voto.
listOf(...) crea una lista non modificabile: gli elementi sono
quelli scritti fra le parentesi, decisi una volta per tutte quando scrivi il programma. Con una
lista del genere si può fare una cosa sola: leggere. E si legge in tutti i modi
che già conosci — con gli indici (giorni[0]), con size, con un ciclo
for, e con i metodi della tabella del §6.3 come sum(),
filter e sorted: è quello che fa l'Esempio 8, che non modifica mai
giorni né voti.
Quello che non si può fare è cambiarla: né aggiungere o togliere elementi
(add, remove, clear), né riordinarla
(sort), né sostituire un elemento con un'assegnazione come
giorni[0] = "dom". Se ci provi, il programma si ferma con un errore come questo:
"Questa lista è immutabile (creata con listOf). Per modificarla usa
mutableListOf(...)". Il rimedio è nella frase stessa: se la lista deve
cambiare, va dichiarata mutableListOf fin dall'inizio.
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ù
Qui trovi i metodi già usati negli esempi. Impara prima size, gli indici,
add e remove. Gli altri li consulti quando ti servono.
Attenzione a due famiglie:
add, clear e sort cambiano la lista e non danno un valore da salvare;
sum, sorted e filter danno un risultato: lo stampi o lo metti in una variabile.
remove risponde true o false secondo che abbia trovato l'elemento.
Le righe che aggiungono, tolgono o riordinano sul posto valgono solo per mutableListOf.
| Metodo | Cosa fa |
|---|---|
lista.size | quanti elementi contiene |
lista[i], lista[i] = x | legge / cambia l'elemento in posizione i (cambiarlo: solo liste modificabili) |
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() | una nuova lista ordinata / al contrario: la lista di partenza non cambia |
sort() | ordina la lista stessa (solo liste modificabili): non restituisce niente, perché il risultato è la lista stessa, già ordinata — non serve riassegnarla. Quindi a.sort() ordina a, ma b = a.sort() non mette nessuna lista in b: per una copia ordinata, lasciando intatta l'originale, si usa b = a.sorted() |
filter { }, map { }, count { } | tiene solo alcuni / li trasforma / li conta |
joinToString(", "), contentToString() | trasforma la collezione in un testo da stampare |
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), lista.removeAt(i) | la versione "a metodo" di lista[i] e lista[i] = x / toglie l'elemento in posizione i |
first(), last() | primo e ultimo elemento |
sortBy { ... }, forEach { } | ordina secondo un criterio / ripete qualcosa per ogni elemento |
distinct(), reversed() | una nuova lista senza duplicati / con l'ordine inverso |
any { }, all { } | almeno uno / tutti |
take(n), drop(n), subList(a, b) | i primi n / tutti tranne i primi n / una parte della lista |
maxOrNull(), minOrNull() | massimo e minimo che danno null su una lista vuota (vedi il riquadro qui sotto) |
toMutableList() | una copia modificabile di una lista letta: serve per aggiungere a un risultato di filter o sorted |
Hanno bisogno di almeno un elemento: se la lista è vuota fermano il programma con un errore.
Quando può capitare (per esempio i voti letti finché l'utente non scrive -1) usa
maxOrNull() e minOrNull(), che danno null, oppure
controlla prima lista.isEmpty().
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.
In filter { it >= 6 }, it è l'elemento di adesso.
Kotlin lo passa uno alla volta. Tieni il voto se è ≥ 6.
Puoi anche scriverlo filter { voto -> voto >= 6 }: stesso lavoro, con un nome scelto da te.
Per ora basta questo. Non è un argomento nuovo: è il modo corto di dire
«per ogni elemento, questa condizione».
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(3) { "" } | array vuoto da riempire dopo: zeri, 0.0, stringhe vuote |
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.
Con i voti 7, 5, 9, 6, 8 le righe sono:
Voti letti: 7, 5, 9, 6, 8, Media: 7.0,
Massimo: 9, Minimo: 5,
Sufficienti: 4 e Insufficienti: [5].
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.
Parti da una lista vuota. Aggiungi, in questo ordine, i testi
pane, latte, mele, pasta.
Poi: inserisci burro in seconda posizione, togli latte,
ordina in ordine alfabetico, stampa con joinToString e mostra quanti prodotti restano.
Le due righe finali sono Lista: burro, mele, pane, pasta e
Prodotti da comprare: 4.
Non serve leggere da tastiera: i prodotti sono questi.
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.
Con i voti 6, 4, 8, 7, 5, 9, 6: tre sopra e quattro sotto.
Le quattro righe sono Voti ordinati: [4, 5, 6, 6, 7, 8, 9],
la media (con i decimali che calcola Kotlin),
Sopra la media: 3 e Sotto la media: 4.
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.
Chiedi all'utente quanti brani vuole inserire, leggi i titoli uno alla volta in una
mutableListOf<String> che parte vuota, poi stampa la playlist completa
e il numero di brani. Alla fine chiedi un titolo da cercare e stampa se c'è, e in che
posizione (contando da 1).
Con 3 brani e ricerca Albachiara le stampe dopo la lettura sono:
Playlist: Albachiara, Sarà perché ti amo, Ma il cielo è sempre blu,
Brani in playlist: 3 e
"Albachiara" è nella playlist, in posizione 1.
La lista nasce vuota con mutableListOf<String>() e cresce con
playlist.add(readln()) a ogni giro del ciclo. Per la ricerca servono
contains e indexOf: attenzione, indexOf conta da 0.
mutableListOf<String>()crea la lista vuota: il numero di brani lo decide l'utente mentre il programma gira, quindi non può essere scritto nel codice.add(readln())legge una riga e la mette in fondo alla lista: dopo il ciclo la playlist contiene i titoli nell'ordine di inserimento.containsrisponde sì/no,indexOfdà la posizione: è a base 0, quindi per parlarne con una persona si aggiunge 1.- Se il titolo mancasse,
indexOfdarebbe -1: per questo la stampa della posizione sta dentro l'ifdelcontains.
Due liste modificabili che viaggiano in parallelo: chiedi quanti libri registrare e, per
ognuno, leggi il titolo in una mutableListOf<String> e il prezzo in una
mutableListOf<Double>. Poi stampa il catalogo riga per riga
(titolo: prezzo), il prezzo medio e il titolo del libro più costoso.
Dopo le domande stampa il titolo === CATALOGO ===, una riga per libro
(I promessi sposi: 12.5 euro), poi
Prezzo medio: 11.2 euro e
Il libro più costoso: I promessi sposi.
Nello stesso giro del ciclo fai due add: così la posizione 0 di entrambe le
liste riguarda lo stesso libro. Per il catalogo scorri titoli.indices e leggi
titoli[i] e prezzi[i]. Per il massimo tieni da parte la
posizione, non solo il prezzo: ti serve per ritrovare il titolo.
- Le due liste partono vuote e crescono insieme: dopo il ciclo,
titoli[i]eprezzi[i]parlano dello stesso libro. È lo schema degli elementi collegati. - Il catalogo si stampa con
titoli.indices: lo stesso indice legge da entrambe le liste. average()funziona direttamente suprezzi: i valori sono tutti nella seconda lista, non serve ricalcolare nulla.- La ricerca del massimo parte da
posizioneMax = 0e tiene l'indice, come nell'Esempio 7c: il prezzo massimo serve solo da confronto, ma il titolo va recuperato con lo stesso indice.
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). Se il numero iniziale è
0 (o la prima riga non è un numero) non leggere nessun voto e stampa solo
Nessun voto inserito. In quel caso non chiamare max() né
average(): su una lista vuota fermano il programma.
Altrimenti stampa, in quest'ordine e con questi testi:
Voti: 7, 0, 9, 6 Quantità: 4 Somma: 22 Media: 5.5 Massimo: 9 Sufficienti: 3
Con l'input di prova 4, 7, assente, 9,
6 i voti sono quattro e il terzo vale 0.
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