CodeGym /Corsi /Kotlin SELF /List e MutableList — ordine, indici, operazioni di base

List e MutableList — ordine, indici, operazioni di base

Kotlin SELF
Livello 19, Lezione 1
Disponibile

1. List e MutableList: cosa sono e in cosa differiscono

Se un array in Kotlin e una “scatola di dimensione fissa” (come un contenitore in magazzino: quante celle ti danno, quelle hai), allora List e una “lista” che puo crescere e ridursi durante l’esecuzione del programma. Ma la cosa principale che rende List davvero una lista e l’ordine: gli elementi sono memorizzati in una sequenza definita e ogni elemento ha una posizione (indice). Kotlin descrive esplicitamente List come una collezione con memorizzazione ordinata e accesso indicizzato.

Allo stesso tempo e importante ricordare una sottigliezza: List e un’interfaccia read-only. Cioe, tramite un riferimento di tipo List puoi leggere, ma non puoi modificare la lista. Se invece ti serve aggiungere, eliminare o sostituire elementi, usi MutableList (che estende le capacita della lista con operazioni di scrittura).

Una piccola tabella di riferimento:

Tipo Si puo leggere per indice Si puo aggiungere/rimuovere Modo tipico di creare
List<T>
si no
listOf(...)
MutableList<T>
si si
mutableListOf(...)

2. Indici e accesso agli elementi

Ordine e indici: perche il primo elemento e 0

Quando lavori con una lista, quasi sempre dici mentalmente “primo”, “secondo”, “ultimo”. Il computer invece dice “0-esimo”, “1-esimo”, “2-esimo”… e cosi via. Gli indici in Kotlin iniziano da zero e arrivano fino a lastIndex, dove lastIndex = size - 1. Non e una “caratteristica di Kotlin”, ma uno stile comune alla maggior parte dei linguaggi.

E utile tenere a mente un piccolo schema:

size = 4
indici: 0  1  2  3
           ^        ^
        primo     lastIndex

Ed e qui che nasce il classico errore da principiante: provare ad accedere a list[size] come se fosse “l’ultimo elemento”. Purtroppo size e il numero di elementi, non l’ultimo indice. L’ultimo indice e size - 1 oppure lastIndex.

Mini-esempio:

fun main() {
    val xs = listOf(10, 20, 30, 40)

    println(xs.size)       // 4
    println(xs.lastIndex)  // 3
}

Lettura degli elementi: list[i] e get(i)

Quando hai gia accettato la filosofia “gli indici iniziano da zero”, il passo successivo e imparare a prendere un elemento. In Kotlin ci sono due forme che fanno la stessa cosa: list[i] e list.get(i). La prima si legge piu facilmente, la seconda a volte e utile quando vuoi sottolineare che si tratta proprio di una chiamata di metodo.

Esempio:

fun main() {
    val words = listOf("one", "two", "three", "four")

    println(words[0])        // one
    println(words.get(2))    // three
    println(words[3])        // four
}

Se l’indice e sbagliato (per esempio, -1 oppure size), il programma andra in errore per fuori limite. E normale: la lista si protegge dal “prendimi un elemento dall’universo parallelo”.

3. Operazioni di base di MutableList

Sostituzione di un elemento per indice

Quando passi da List a MutableList, inizia la magia: possiamo modificare gli elementi. L’operazione piu semplice e sostituire il valore in una posizione esistente. La sintassi sembra quella di un array: list[i] = newValue.

Internamente e l’equivalente di set(i, newValue), ma la notazione con [] e piu leggibile.

Esempio:

fun main() {
    val items = mutableListOf("a", "b", "c")

    items[1] = "B"          // abbiamo sostituito l’elemento con indice 1
    println(items)          // [a, B, c]
}

Qui c’e un significato importante: abbiamo sostituito l’elemento nella posizione 1. Non abbiamo “inserito” nulla, la dimensione non e cambiata e gli altri elementi non si sono spostati.

Aggiunta di elementi: add(value)

Ora parliamo della crescita della lista. Nei problemi reali quasi sempre inizi con una lista vuota e la riempi gradualmente: l’utente inserisce task, prodotti nel carrello, voci di spesa, nomi dei partecipanti — qualunque cosa.

MutableList.add(value) aggiunge un elemento in fondo alla lista. E una delle operazioni piu frequenti, ed e comodo pensarla come “scrivere in fondo”.

Esempio:

fun main() {
    val numbers = mutableListOf(1, 2, 3)

    numbers.add(4)
    numbers.add(5)

    println(numbers)        // [1, 2, 3, 4, 5]
    println(numbers.size)   // 5
}

Se provi una leggera gioia nel vedere che size cresce, congratulazioni: hai appena vissuto la “prima crescita del contenitore”. Un array non sa farlo.

Rimozione per indice: removeAt(index) e l’effetto di spostamento

La rimozione e il punto in cui gli indici iniziano a comportarsi da indici. Cioe, dopo aver rimosso un elemento, tutti gli elementi a destra “scorrono a sinistra” e i loro indici cambiano. E logico: se in una fila di persone una se ne va, tutti quelli dietro si avvicinano all’inizio.

removeAt(index) rimuove l’elemento alla posizione index e restituisce l’elemento rimosso. E comodo: puoi stampare subito cosa hai rimosso oppure riusare il valore piu avanti.

Esempio:

fun main() {
    val items = mutableListOf("x", "y", "z")

    val removed = items.removeAt(0)
    println(removed)        // x
    println(items)          // [y, z]
}

Schema visivo dello spostamento:

prima:  [ x, y, z ]
indici   0  1  2

removeAt(0)

dopo:  [ y, z ]
indici  0  1

Se conservi dei “numeri importanti” degli elementi (per esempio, l’utente ha scelto “rimuovi l’elemento n. 2”), dopo una rimozione i numeri degli altri cambiano. Non e un bug, e matematica.

Rimozione per valore: remove(value)

MutableList ha anche un altro modo di rimuovere: remove(value). A differenza di removeAt, non lavora con la posizione, ma con il valore stesso: “rimuovi l’elemento "bread"”.

Qui iniziano due idee importanti.

La prima: remove(value) restituisce Boolean. true — se la rimozione e avvenuta, false — se quel valore non esisteva (cioe la lista non e cambiata).

La seconda: se ci sono piu valori uguali, allora remove(value) rimuove la prima occorrenza trovata, non “tutte in una volta”.

Esempio:

fun main() {
    val products = mutableListOf("milk", "bread", "tea", "bread")

    val ok1 = products.remove("bread")
    println(ok1)            // true
    println(products)       // [milk, tea, bread]

    val ok2 = products.remove("coffee")
    println(ok2)            // false
    println(products)       // [milk, tea, bread]
}

Nota: "bread" compariva due volte, e dopo la rimozione ne e rimasta una. E normale: abbiamo rimosso solo la prima occorrenza.

Verifica di presenza: contains(value) e l’operatore in

Quando lavori con le liste, spesso serve la domanda “c’e gia?”. Per esempio, “questo prodotto e gia nel carrello?”, “questo comando e supportato?”, “questo task e gia stato aggiunto?”.

Puoi scrivere list.contains(value), oppure — piu leggibile — value in list. Il significato e lo stesso, solo che la seconda forma e piu vicina al linguaggio naturale.

Mini-esempio:

fun main() {
    val allowed = listOf("add", "list", "remove")

    println(allowed.contains("list"))  // true
    println("add" in allowed)          // true
    println("exit" in allowed)         // false
}

La lista puo contenere duplicati (a differenza di Set), e contains / in rispondono solo “esiste almeno un elemento cosi oppure no”. Kotlin sottolinea che List ammette duplicati e memorizza gli elementi in ordine.

4. Pratica: “Lista della spesa v0”

Ora realizzeremo un piccolo frammento di applicazione che crescera insieme al corso. Per ora senza comandi complessi e senza architettura: mostreremo semplicemente come MutableList aiuta a conservare i dati e come l’input dell’utente permette di aggiungere e rimuovere.

Faremo un modello il piu semplice possibile: una lista di stringhe MutableList<String>, dove ogni stringa e un prodotto.

Stampa della lista “in modo umano”

Iniziamo con la funzione di stampa. Sappiamo gia usare i cicli, quindi iteriamo sui prodotti e li mostriamo con i numeri. Nota: il numero per una persona inizia da 1, mentre l’indice nella lista inizia da 0. Quindi “numero umano = indice + 1”.

fun printShoppingList(items: List<String>) {
    println("Lista della spesa:")
    for (i in 0..items.lastIndex) {
        println("${i + 1}. ${items[i]}")
    }
}

Se la lista e vuota, 0..items.lastIndex sembrera strano (per esempio, 0..-1). Per evitare sorprese, aggiungiamo una semplice protezione.

fun printShoppingList(items: List<String>) {
    if (items.isEmpty()) {
        println("La lista della spesa e vuota.") // La lista della spesa e vuota.
        return
    }

    println("Lista della spesa:")
    for (i in 0..items.lastIndex) {
        println("${i + 1}. ${items[i]}")
    }
}

Aggiunta di un prodotto: add(...) e trim()

Ora scriviamo una funzione che legge una stringa, ripulisce gli spazi e la aggiunge alla lista. Qui usiamo il pattern familiare “letto → preparato → usato”.

fun addItem(items: MutableList<String>) {
    print("Inserisci un prodotto: ")
    val raw = readln()
    val name = raw.trim()

    if (name.isEmpty()) {
        println("Nome vuoto — non aggiungo.") // Nome vuoto — non aggiungo.
        return
    }

    items.add(name)
    println("Aggiunto: $name") // per esempio: Aggiunto: milk
}

Rimozione per numero: removeAt(index) e conversione “numero → indice”

Ora facciamo la rimozione per numero inserito dall’utente (1, 2, 3, …). Dobbiamo trasformare il numero in indice: index = number - 1. E dobbiamo assolutamente controllare i limiti, altrimenti ciao, errore a runtime.

fun removeItemByNumber(items: MutableList<String>) {
    print("Inserisci il numero del prodotto da rimuovere: ")
    val number = readln().trim().toIntOrNull()

    if (number == null) {
        println("Non e un numero.") // Non e un numero.
        return
    }

    val index = number - 1
    if (index < 0 || index > items.lastIndex) {
        println("Non esiste un prodotto con numero $number.") // Non esiste un prodotto con numero 10.
        return
    }

    val removed = items.removeAt(index)
    println("Rimosso: $removed") // per esempio: Rimosso: milk
}

Mini-main: solo per sentire che la lista vive

Per ora non costruiamo un vero menu di comandi (si trasforma facilmente in un “piccolo sistema operativo”), ma facciamo semplicemente 3 passi: aggiungere due prodotti, stampare, rimuovere per numero, stampare di nuovo.

fun main() {
    val items = mutableListOf<String>()

    addItem(items)
    addItem(items)
    printShoppingList(items)

    removeItemByNumber(items)
    printShoppingList(items)
}

Se lo esegui e provi a inserire valori, sentirai la cosa principale: MutableList e un contenitore comodo da modificare, e le operazioni add/removeAt fanno esattamente cio che c’e scritto sulla scatola.

5. Sfumature utili e promemoria

val per MutableList — va benissimo

A questo punto molti commettono un errore logico (e molto umano): “Se la lista cambia, allora la variabile deve essere var”. In realta, nella maggior parte dei casi la variabile dovrebbe essere val, perche non hai intenzione di riassegnare il riferimento a un’altra lista — vuoi cambiare il contenuto della lista corrente.

Esempio di “normalita corretta”:

fun main() {
    val items = mutableListOf("a", "b")

    items.add("c")
    items[0] = "A"

    println(items) // [A, b, c]
}

val impedisce di fare cosi:

items = mutableListOf("x") // non si puo, ma di solito non ci serve

Pero non impedisce di chiamare add, removeAt, remove e di modificare gli elementi. E come dire: “l’indirizzo di casa e fisso, ma i mobili dentro si possono spostare”.

Tabella-promemoria delle operazioni di MutableList

A volte e utile avere un promemoria compatto per non confondere i metodi (soprattutto remove e removeAt — amano sgattaiolare di notte e scambiarsi il significato).

Operazione Esempio Cosa fa Cosa restituisce
Lettura
x = list[i]
Prende l’elemento per indice
T
Sostituzione
list[i] = v
Sostituisce l’elemento per indice
Unit
Aggiungere
list.add(v)
Aggiunge in fondo
Boolean
Rimuovere per indice
list.removeAt(i)
Rimuove l’elemento alla posizione
i
elemento rimosso
T
Rimuovere per valore
list.remove(v)
Rimuove la prima occorrenza
v
Boolean
Verificare presenza
v in list
C’e almeno un
v
Boolean

6. Errori tipici quando si lavora con List e MutableList

Errore n. 1: confondere size e lastIndex.
E molto facile voler scrivere list[list.size] e aspettarsi “l’ultimo elemento”, perche “size e la fine”. Ma size e il numero di elementi e gli indici vanno da 0 a size - 1 (cioe lastIndex). Evitare l’errore e semplice: “l’ultimo elemento” e list[list.lastIndex], non list[list.size].

Errore n. 2: provare a modificare una List creata con listOf.
listOf(...) restituisce una List read-only. E perfetta per una “lista di costanti” o un “insieme temporaneo di valori”, ma se poi vuoi usare add/remove, ti serve mutableListOf(...) e il tipo MutableList.

Errore n. 3: confondere removeAt(index) e remove(value).
E un classico del tipo “ho preso la chiave sbagliata”. removeAt(2) rimuove l’elemento alla posizione 2, mentre remove("2") (se lo scrivessi cosi) cercherebbe di rimuovere il valore "2". Aiuta un’abitudine: se nel nome c’e At, allora si lavora “sul posto”, per indice.

Errore n. 4: dimenticare che dopo removeAt gli elementi si spostano.
I principianti a volte salvano un indice, rimuovono un elemento e poi provano ad accedere al “successivo” usando il vecchio indice, sorprendendosi del comportamento. Dopo la rimozione la lista cambia: tutti gli elementi a destra “si spostano” a un indice in meno, ed e normale. Se scrivi codice che rimuove piu elementi di seguito, devi tenere conto di questo spostamento e agire con attenzione.

Errore n. 5: pensare che remove(value) rimuova tutte le occorrenze.
Quando nella lista ci sono duplicati, remove(value) rimuove solo la prima occorrenza. Questo corrisponde al significato della lista: l’ordine e importante e i duplicati sono ammessi. Se ti aspettavi “rimuovi tutto”, allora ti aspettavi un’altra operazione (ma oggi non la introduciamo — prima impariamo a gestire con sicurezza la meccanica di base).

Errore n. 6: fare var items = mutableListOf(...) “perche la lista cambia”.
Di solito e meglio val: cambi il contenuto, non riassegni la variabile a una nuova lista. var serve se davvero prevedi di sostituire l’intera lista. La disciplina del val di default rende il codice piu tranquillo: meno punti in cui “improvvisamente hanno riassegnato tutto” e meno probabilita di perdere dati.

1
Compito
Kotlin SELF, livello 19, lezione 1
Bloccato
Scheda dei linguaggi
Scheda dei linguaggi
1
Compito
Kotlin SELF, livello 19, lezione 1
Bloccato
Selezione dei partecipanti
Selezione dei partecipanti
1
Compito
Kotlin SELF, livello 19, lezione 1
Bloccato
Correzione dei voti
Correzione dei voti
1
Compito
Kotlin SELF, livello 19, lezione 1
Bloccato
Cambio nella squadra
Cambio nella squadra
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION