Cicli: ripetere le istruzioni
Un computer è fatto per ripetere: stampare 100 righe, sommare i voti di una classe, controllare tutti i caratteri di una parola. I cicli fanno eseguire lo stesso blocco di codice molte volte, cambiando un valore ogni volta.
forcon gli intervalli:1..10,0 until 5,10 downTo 1,step 2;whileedo ... while;breakecontinueper uscire o saltare un giro;- contatori e accumulatori (i due schemi che userai sempre);
- cicli su stringhe e su collezioni.
Ripasso lampo: le condizioni
I cicli ripetono, ma decidono le condizioni: ripasso del capitolo 4. Un salto indietro al capitolo 3, invece, serve per ricordare come si legge un numero dall'input.
Che cosa stampa questo programma?
fun main() {
val x = 5
val y = if (x > 3) "grande" else "piccolo"
println(y)
}grandeEsatto: if restituisce un valore, che finisce nella variabile y.piccoloLa condizione 5 > 3 è vera, quindi si prende il primo ramo.if non può dare un valoreIn Kotlin if è un'espressione: puoi assegnarlo a una variabile.In Kotlin if è un'espressione: il ramo che viene eseguito restituisce un valore, e quel valore si può assegnare a una variabile con val y = if (...) a else b.
L'utente scrive 4, poi 7, poi la parola pippo. Che cosa succede?
fun main() {
var totale = 0
for (i in 1..3) {
print("Numero $i: ")
totale += readln().toInt()
}
println("Totale: $totale")
}toInt() accetta solo cifreEsatto: toInt() non sa convertire pippo e interrompe il programma. Per non fermarsi servirebbe readln().toIntOrNull() ?: 0.Totale: 11, perché pippo viene saltatoNo: toInt() non salta i dati che non capisce, si ferma con un errore.Totale: 47pippo, attaccando i tre valoriNo: totale è un Int e toInt() restituisce numeri, non testo.Dentro un ciclo l'errore capita una volta per ogni giro: un dato non numerico ferma tutto al giro in cui compare. La forma robusta è readlnOrNull()?.toIntOrNull() ?: 0, spiegata nel capitolo 3.
5.1 for su un intervallo
for (i in 1..5) ripete il blocco facendo valere i uguale a 1, 2, 3, 4 e 5.
I due puntini .. creano un intervallo che comprende entrambi gli estremi.
La variabile i esiste solo dentro al ciclo.
Quante volte viene eseguito il corpo di questo ciclo?
fun main() {
for (i in 3..1) {
println(i)
}
}3..1 è un intervallo vuoto, non un conto alla rovescia... il conteggio va sempre dal più piccolo al più grande. Per scendere serve downTo.Per scendere: for (i in 3 downTo 1), che stampa 3 2 1.
0 until 3comprende0, 1, 2: il 3 è escluso, per questountilè comodo con gli indici.10 downTo 0 step 5parte da 10 e scende di 5 in 5:10, 5, 0. CondownTol'estremo finale è compreso.println()senza argomenti non stampa testo: manda solo a capo, così le due sequenze finiscono su righe diverse.
| Scrivi | Valori che prende la variabile |
|---|---|
1..5 | 1, 2, 3, 4, 5 (estremi compresi) |
0 until 5 | 0, 1, 2, 3, 4 (l'ultimo valore è escluso) |
10 downTo 6 | 10, 9, 8, 7, 6 (conta all'indietro) |
1..10 step 2 | 1, 3, 5, 7, 9 (a salti) |
Quando un ciclo deve passare su tutti gli elementi di qualcosa che ha dimensione n,
si scrive 0 until n: gli indici vanno da 0 a n-1, quindi l'ultimo valore da visitare è
proprio n-1. Rivedrai questo schema nel capitolo 6.
Nel laboratorio sopra il primo for sceglie la riga, il secondo
genera le colonne. Quando l'esterno passa alla riga successiva, l'interno
non riprende da dove era rimasto: ricomincia da 1. Per questo la tavola cresce
in due direzioni: lungo la riga (1 2 3) e scendendo di riga in riga
(1 2 3, poi 2 4 6, poi 3 6 9).
5.2 while: ripetere finché...
while (condizione) { ... } esegue il blocco finché la condizione resta vera.
Serve quando non sai in anticipo quante volte ripetere. Attenzione: dentro al ciclo qualcosa deve
cambiare, altrimenti si gira per sempre.
Devi chiedere un dato almeno una volta, e poi ripetere finché l'utente non decide di smettere. Quale ciclo usi?
whileCon while la condizione è controllata prima: se è subito falsa, il blocco non viene mai eseguito.do { ... } while (...)Esatto: do ... while esegue il blocco e controlla la condizione dopo, quindi gira almeno una volta.for (i in 1..10)Con for sai in anticipo quante volte ripetere; qui non lo sai.Schema tipico: do { dato = readln().toInt() } while (dato != 0).
5.3 do ... while: almeno una volta
do { ... } while (condizione) esegue il blocco prima e controlla la
condizione dopo: serve quando il blocco deve girare almeno una volta
(tipico nei menu "ripeti finché non scegli esci").
- Dimenticare di cambiare la variabile di controllo (
i++). - Sbagliare la condizione:
while (i < 10)quando l'obiettivo è arrivare a 10 compreso. - Usare una condizione sempre vera, per esempio
while (true)senza unbreakdentro.
Il manuale ti avvisa con "il programma ha eseguito troppe istruzioni".
5.4 break e continue
break esce subito dal ciclo, continue salta al giro successivo.
Sono comodi per fermarsi appena hai trovato quello che cercavi o per ignorare i casi che non ti interessano.
La forma robusta readlnOrNull()?.toIntOrNull() ?: 0 usa quasi sempre
?: 0, ma dentro un ciclo quello zero fa due cose diverse a seconda del ciclo.
Nell'esempio 5 lo 0 è anche la sentinella che ferma i conti:
un dato sporco viene letto come 0 e chiude il ciclo, come se
l'utente avesse scritto 0 per fermarsi — va bene solo se perdere i dati rimasti è accettabile.
Nei cicli che girano un numero fisso di volte (esempio 7) lo 0 non ferma niente:
entra nei calcoli come numero qualsiasi e il ciclo prosegue. Prima di scrivere
?: qualcosa dentro un ciclo chiediti: "che cosa deve succedere al ciclo se
questo dato non si capisce?" Se deve fermarsi, scegli come valore di riserva proprio la
sentinella; se deve andare avanti, qualunque numero va bene.
continuesalta il resto del giro: quandoivale3non si arriva allaprint, e il ciclo riparte con4.breakesce dal ciclo: quandoivale5il ciclo finisce subito, prima di stampare.- Per questo l'output è
1 2 4: il 3 è saltato, il 5 non è mai stampato.
L'esempio 5 si fermava quando l'utente scriveva 0; questo si ferma quando i dati
finiscono davvero — premi Ctrl+D (o il pulsante
⏹ fine dei dati) e il ciclo esce da solo, anche senza leggere nessun numero. È lo
schema che usano i programmi che leggono un file riga per riga: non c'è un valore magico che
significa "basta", c'è la fine dei dati. Due dettagli da notare: la variabile è dichiarata
String? — senza il punto interrogativo il tipo dedotto sarebbe una stringa normale,
e la lettura di null darebbe l'errore "la variabile è di tipo String e non può
valere null"; e dentro il ciclo la lettura sporca usa ?: 0, perché qui un dato non
capito non deve fermare niente — il ciclo si ferma solo alla fine dei dati.
5.5 I due schemi fondamentali: contatore e accumulatore
Il contatore conta quante volte succede qualcosa; l'accumulatore somma (o moltiplica) dei valori uno dopo l'altro. Sono lo schema di metà degli esercizi sui cicli.
5.6 Cicli su testo e su collezioni
Lo stesso for serve per scorrere un testo carattere per carattere o una collezione
elemento per elemento: in questi casi non si usa l'intervallo, ma direttamente il valore.
Stai cercando una parola in una lista e vuoi smettere appena l'hai trovata. Che cosa metti dentro l'if?
break: esce subito dal cicloEsatto: break interrompe il ciclo, quindi non si fanno giri inutili.continue: salta al giro successivoNo: continue salta questo giro e continua a cercare, ma tu vuoi fermarti.break = esci dal ciclo; continue = salta questo giro e passa al successivo.
for (nome in nomi) significa "per ogni nome dentro nomi".
Usa il singolare per l'elemento e il plurale per la collezione: il codice diventa una frase leggibile.
listOf in anteprima: lo vedrai al capitolo 6
Qui basta leggere listOf("Anna", "Luca", "Sara") come "l'elenco di questi tre nomi".
Come si crea, si modifica e si scorre una lista è spiegato per davvero nel
capitolo 6: per adesso non serve ricordarne la sintassi.
5.7 Riepilogo
| Forma | Quando usarla |
|---|---|
for (i in 1..n) | sai quante volte ripetere, estremi compresi |
for (i in 0 until n) | indici di una collezione di dimensione n |
for (x in collezione) | per ogni elemento della collezione |
while (condizione) | non sai quante volte: ripeti finché serve |
do { ... } while (c) | il blocco deve girare almeno una volta |
break / continue | esci dal ciclo / salta al giro dopo |
5.8 Esercizi guidati
Leggi un numero n e calcola la somma di tutti i numeri da 1 a n
(per esempio con n = 10 la somma è 55). Stampa anche la media dei numeri.
Prima del ciclo dichiara var somma = 0; dentro al ciclo fai somma += i.
La media è somma.toDouble() / n.
var somma = 0è l'accumulatore: parte da zero e cresce a ogni giro.for (i in 1..n)ripete il blocco per ogni numero da 1 a n compreso.somma.toDouble() / nevita la divisione intera: senza la conversione la media di 10 numeri sarebbe stata troncata.- Il ciclo è più sicuro della formula matematica n*(n+1)/2? No: è solo più leggibile per un principiante, ed è lo stesso schema che serve per somme di valori letti da input.
Leggi un numero e stampa la sua tabellina da 1 a 10 nel formato 7 x 3 = 21.
Alla fine stampa la somma di tutti i risultati.
Il risultato di ogni riga è numero * i: mettilo in una variabile, stampalo e aggiungilo
all'accumulatore.
- Il ciclo va da 1 a 10 perché
1..10comprende entrambi gli estremi. - Il risultato viene calcolato una volta sola e salvato in una
val: così non si ripete il conto due volte (nella stampa e nella somma). - L'accumulatore
sommaè fuori dal ciclo: se fosse dentro, verrebbe azzerato a ogni giro.
Leggi un numero n e stampa i numeri da 1 a n con queste regole:
se il numero è divisibile per 3 stampa Fizz, se è divisibile per 5 stampa Buzz,
se è divisibile per entrambi stampa FizzBuzz, altrimenti il numero stesso.
Conta anche quante volte compare FizzBuzz.
Controlla prima il caso "divisibile per 3 e per 5"
(i % 3 == 0 && i % 5 == 0), altrimenti gli altri due lo catturano prima.
- L'ordine delle condizioni è decisivo: il caso 15 (divisibile per 3 e per 5) deve essere controllato per primo.
- Se avessi messo
i % 3 == 0come prima condizione, il 15 sarebbe finito in "Fizz" e "FizzBuzz" non comparirebbe mai. - Il contatore
quantiFizzBuzzè dichiaratovarfuori dal ciclo e incrementato solo nel primo ramo. println(i)accetta un numero: Kotlin lo trasforma in testo per stamparlo.
Questo programma parte senza errori, ma stampa Somma: 10 invece di
Somma: 15. Deve sommare i numeri da 1 a 5. Trova l'errore logico nell'intervallo
e correggilo: il programma deve stampare 15.
until esclude l'ultimo valore: 1 until 5 dà 1, 2, 3, 4. Per includere
il 5 serve 1..5 oppure 1 until 6.
- Il codice compila e gira: l'errore è logico.
1 until 5produce 1, 2, 3, 4 e la somma è 10. - Con
1..5gli estremi sono entrambi compresi: 1, 2, 3, 4, 5 e la somma è 15. - La stessa correzione si poteva scrivere come
1 until 6:untilesclude sempre l'ultimo valore. - Quando il risultato è sbagliato ma non c'è nessun messaggio di errore, il sospettato numero uno è l'intervallo del ciclo.
Il programma deve stampare la somma dei numeri da 1 a 5 (cioè 15), ma non finisce mai. Interrompilo con ▶ Esegui dopo aver capito il problema e correggilo.
Dentro un while qualcosa deve far prima o poi diventare falsa la condizione: qui manca l'istruzione che fa crescere i.
iresta sempre1: la condizionei <= 5è vera per sempre e il ciclo non finisce (ciclo infinito).- Il manuale ti avvisa con "il programma ha eseguito troppe istruzioni", come farebbe un vero programma che si blocca.
- Aggiungendo
i++dentro al ciclo,idiventa 2, 3, 4, 5, 6: quando vale 6 la condizione è falsa e il ciclo si ferma. - Lo stesso schema con
for (i in 1..5)non ha questo rischio, perché è ilforstesso a far crescerei.
for con .., until, downTo e stepfor, while e do ... whilebreak e continue e so spiegare la differenza