Vai al contenuto

Capitolo 4 · Strutture iterative

Cosa impari

  • a ripetere un blocco di istruzioni con il ciclo while, finché una condizione è vera;
  • a ripetere un blocco per ogni elemento di una sequenza con il ciclo for e la funzione range;
  • a usare gli operatori di assegnazione sintetica (+=, -=, ...) e gli accumulatori;
  • a fermare un ciclo con un valore sentinella e a controllare i dati con un ciclo di validazione;
  • a scrivere cicli annidati, cioè un ciclo dentro un altro.

Spesso un programma deve fare le stesse operazioni più volte: calcolare la provvigione di molti venditori, sommare tanti numeri, controllare un dato finché non è corretto. Copiare le stesse istruzioni tante volte rende il programma lungo e ripetitivo. Inoltre, se trovi un errore, devi correggerlo in tutte le copie. Le strutture iterative risolvono il problema: scrivi il codice una volta sola e Python lo ripete.

Le strutture iterative

Una struttura iterativa (o ciclo, in inglese loop) esegue ripetutamente un'istruzione o un gruppo di istruzioni. Ne esistono due tipi:

Tipo di ciclo Come decide quando fermarsi In Python
controllato da una condizione usa una condizione vera/falsa: continua finché è True while
controllato da un contatore ripete il blocco un numero di volte stabilito for

Il ciclo while

while condizione:
    istruzione
    istruzione

Il ciclo while ha due parti:

  • la condizione da controllare;
  • il corpo, cioè il blocco di istruzioni rientrate da ripetere.

Python controlla la condizione. Se è vera esegue il corpo, poi torna su e controlla di nuovo la condizione. Quando la condizione diventa falsa, il programma esce dal ciclo e prosegue con l'istruzione dopo il blocco. Come per l'if, la riga del while finisce con i due punti e il corpo è rientrato.

Nel diagramma di flusso la condizione è un rombo: dall'uscita vero si va al corpo del ciclo, che poi torna al rombo; dall'uscita falso si esce dal ciclo.

Ogni singola esecuzione del corpo si chiama iterazione. Nell'esempio seguente la variabile numero parte da 1 e cresce di 1 a ogni iterazione.

numero = 1
while numero <= 5:
    print('Iterazione numero', numero)
    numero = numero + 1
print('Fine del ciclo: numero vale', numero)

Alla quinta iterazione numero diventa 6, la condizione 6 <= 5 è falsa e il ciclo termina.

La condizione di controllo

Perché il ciclo finisca, dentro il corpo deve succedere qualcosa che rende falsa la condizione. Nell'esempio di prima è l'istruzione numero = numero + 1.

Il while si chiama anche ciclo a pre-test (pretest loop): la condizione è controllata prima di ogni iterazione. Di conseguenza:

  • se la condizione è falsa già all'inizio, il corpo non viene mai eseguito;
  • prima del ciclo servono delle operazioni preliminari, ad esempio dare un valore iniziale alla variabile della condizione.

Esempio: calcolo delle provvigioni

Un venditore riceve una provvigione, cioè una percentuale sulle vendite. Il programma calcola la provvigione e poi chiede se si vuole fare un altro calcolo. Il ciclo continua finché l'utente risponde s.

keep_going = 's'
while keep_going == 's':
    sales = float(input('Inserire il totale delle vendite: '))
    comm_rate = float(input('Inserire la percentuale delle provvigioni: '))
    commission = sales * comm_rate
    print(f'Le provvigioni sono €{commission:,.2f}.')
    keep_going = input("Vuoi calcolare un'altra provvigione? (inserisci s per Sì): ")
print('Fine del programma.')

Prova con vendite 10000 e percentuale 0.1 (provvigione 1,000.00 €), rispondi s, poi prova con 20000 e 0.15 e rispondi n.

Nota l'operazione preliminare: keep_going = 's' prima del ciclo. Senza di essa la prima condizione non si potrebbe controllare, perché la variabile non esisterebbe ancora.

Il ciclo infinito

Un ciclo infinito è un ciclo che non ha al suo interno nessun modo per fermarsi: si ripete finché qualcuno non interrompe il programma a mano. Di solito succede perché il programmatore si dimentica di modificare la variabile della condizione. Il codice seguente non va eseguito: numero resta sempre 1 e la condizione resta sempre vera.

numero = 1
while numero <= 5:
    print('Iterazione numero', numero)
    # manca numero = numero + 1: il ciclo non finisce mai!

Cicli infiniti voluti

A volte un ciclo infinito è voluto: un server web, il motore di un videogioco o un programma che aspetta i clic dell'utente devono restare sempre attivi. Nei nostri esercizi, invece, è sempre un errore.

Il ciclo for

Il ciclo for è un ciclo controllato da un contatore: esegue un numero stabilito di iterazioni. È pensato per lavorare con le sequenze di elementi: ripete il corpo una volta per ogni elemento della sequenza.

for variabile in [valore1, valore2, valore3]:
    istruzione
    istruzione

La variabile si chiama variabile target. All'inizio di ogni iterazione riceve un elemento della sequenza:

  • alla prima iterazione riceve il primo valore, poi viene eseguito il corpo;
  • alla seconda iterazione riceve il secondo valore, e così via, in ordine, fino all'ultimo elemento.
for name in ['Mario', 'Luca', 'Anna']:
    print(name)

Il corpo viene eseguito tre volte, una per ogni nome. I valori tra parentesi quadre possono essere anche numeri.

for num in [1, 2, 3, 4, 5]:
    print(num, 'al quadrato fa', num ** 2)

Anche una stringa è una sequenza

Nel capitolo 3.5 hai visto che una stringa è una sequenza di caratteri. Quindi il for può scorrere una stringa un carattere alla volta: for c in 'ciao': esegue il corpo 4 volte, con c che vale 'c', 'i', 'a', 'o'.

La funzione range

Scrivere a mano tutti i valori tra parentesi quadre è scomodo: pensa a un ciclo da 1000 iterazioni! La funzione range crea per te una sequenza di numeri interi su cui il for può iterare. Una sequenza su cui si può iterare si chiama iterabile.

range può ricevere 1, 2 o 3 argomenti:

Chiamata Sequenza prodotta Significato
range(5) 0, 1, 2, 3, 4 da 0 fino al limite superiore, escluso
range(1, 6) 1, 2, 3, 4, 5 dal limite inferiore (incluso) al limite superiore (escluso)
range(1, 10, 2) 1, 3, 5, 7, 9 il terzo numero è il passo (step): la distanza tra un numero e il successivo
range(10, 0, -1) 10, 9, ..., 1 con passo negativo la sequenza è decrescente
print('range(5):')
for x in range(5):
    print(x)
print('range(1, 10, 2):')
for x in range(1, 10, 2):
    print(x)
print('Conto alla rovescia:')
for x in range(10, 0, -1):
    print(x)
print('Partenza!')

Il limite superiore è escluso

range(1, 10) si ferma a 9. Se vuoi arrivare fino a un numero n compreso, scrivi range(1, n + 1). È la stessa regola dello slicing delle stringhe: end è escluso.

Un for con range(n) è il modo più semplice per ripetere un'azione n volte, anche se la variabile target non serve dentro il corpo.

for x in range(3):
    print('Ciao!')

Operatori di assegnazione sintetica

In molte assegnazioni la variabile a sinistra dell'= compare anche a destra, come in x = x + 5. Gli operatori di assegnazione sintetica permettono di scriverlo in modo più breve.

Operatore Esempio Cosa fa Equivale a
+= x += 5 aggiunge 5 a x x = x + 5
-= x -= 3 sottrae 3 da x x = x - 3
*= x *= 10 moltiplica x per 10 x = x * 10
/= x /= 2 divide x per 2 x = x / 2
%= x %= 4 assegna a x il resto di x / 4 x = x % 4
**= x **= 2 eleva x alla seconda x = x ** 2
x = 10
x += 5
print(x)    # 15
x -= 3
print(x)    # 12
x *= 10
print(x)    # 120
x /= 2
print(x)    # 60.0: la divisione / dà sempre un float
x %= 7
print(x)    # 4.0
x **= 2
print(x)    # 16.0

Accumulatori: la somma cumulativa

Molti programmi devono calcolare il totale di una serie di numeri. Si usa una variabile detta accumulatore:

  1. prima del ciclo l'accumulatore vale 0;
  2. a ogni iterazione si aggiunge un numero all'accumulatore, con +=;
  3. alla fine del ciclo l'accumulatore contiene il totale.

Durante il ciclo l'accumulatore contiene la somma parziale, cioè la somma dei numeri visti fino a quel momento.

# Somma cumulativa dei numeri da 1 a 10
somma_cumulativa = 0
for num in range(1, 11):
    somma_cumulativa += num
    print('La somma parziale è: ' + str(somma_cumulativa))
print('Il totale è', somma_cumulativa)

Inizializza l'accumulatore prima del ciclo

Se scrivi somma_cumulativa = 0 dentro il ciclo, l'accumulatore torna a 0 a ogni iterazione e alla fine contiene solo l'ultimo numero. Se non lo scrivi affatto, Python segnala un NameError alla prima istruzione +=.

L'accumulatore può raccogliere anche numeri inseriti dall'utente.

totale = 0.0
for i in range(1, 4):
    spesa = float(input(f'Spesa del giorno {i}: '))
    totale += spesa
print(f'Spesa totale: {totale:.2f} euro')

Esempio: tabella dei quadrati

L'utente sceglie fino a quale numero calcolare i quadrati. Poiché il limite superiore di range è escluso, si scrive range(1, end + 1). La sequenza di escape \t (tabulazione) incolonna i valori.

print('Questo programma mostra una lista di numeri e il loro quadrato')
end = int(input('Fino a che numero devo calcolare il quadrato? '))
print('Numero\tQuadrato')
for numero in range(1, end + 1):
    quadrato = numero ** 2
    print(f'{numero}\t{quadrato}')

Sentinelle

Spesso non sai in anticipo quanti dati inserirà l'utente. Una soluzione è la sentinella: un valore speciale che indica la fine della sequenza di dati. Quando il programma legge la sentinella, capisce che i dati sono finiti ed esce dal ciclo.

La sentinella deve essere diversa da tutti i dati validi. Ad esempio, se i dati sono pesi (sempre maggiori di zero), si può usare 0 oppure -1. Quando si legge un file, una riga vuota può fare da sentinella.

Nell'esempio che segue l'utente inserisce il peso di alcuni pazienti; lo 0 fa da sentinella. Il programma usa un accumulatore (totale) e un contatore (conta), cioè una variabile che aumenta di 1 a ogni dato letto.

print('Inserire il peso di un paziente o inserire 0 per terminare')
peso = int(input('Peso: '))
totale = 0
conta = 0
while peso != 0:
    print(f'Il paziente pesa: {peso} kg')
    totale += peso
    conta += 1
    print('Inserire il peso di un paziente o inserire 0 per terminare')
    peso = int(input('Peso: '))

if conta > 0:
    print(f'Il peso medio di {conta} pazienti è: {totale / conta} kg')
else:
    print('Nessun peso inserito.')

Prova con 70, 80, 90 e poi 0: la media è 80.0 kg. Osserva lo schema:

  • il primo dato si legge prima del ciclo (operazione preliminare);
  • i dati successivi si leggono alla fine del corpo, così la condizione controlla subito il nuovo valore;
  • la sentinella non viene sommata né contata.

L'if finale evita la divisione per zero quando l'utente inserisce subito 0.

Versione con le liste

Questo esempio si può scrivere anche con una lista (pesi.append(peso)) e con le funzioni sum e len. Le liste si studiano nel capitolo 7; per ora accumulatore e contatore bastano.

Validazione dell'input

GIGO (Garbage In, Garbage Out, "spazzatura dentro, spazzatura fuori"): se l'utente inserisce dati sbagliati, il programma li elabora comunque come se fossero corretti. Il risultato è sbagliato, anche se il calcolo è fatto bene.

La validazione dell'input consiste nel controllare i dati inseriti prima di usarli in un calcolo. Di solito si fa con un ciclo di validazione, scritto con un while:

  • si legge il dato;
  • finché il dato non è valido, si mostra un messaggio di errore e se ne chiede uno nuovo;
  • quando il dato è valido, si esce dal ciclo e si continua.
score = int(input('Inserire un voto: '))
while score < 0:
    print('ERRORE: il voto non può essere negativo.')
    score = int(input('Inserire il voto corretto: '))
print('Voto accettato:', score)

Prova con -5, poi -1, poi 27: i primi due valori vengono rifiutati.

La condizione del while descrive il dato sbagliato. Per controllare un intervallo usa or, come nel capitolo 3: un voto è sbagliato se è minore di 0 oppure maggiore di 30.

score = int(input('Inserire un voto (0-30): '))
while score < 0 or score > 30:
    print('ERRORE: il voto deve essere compreso tra 0 e 30.')
    score = int(input('Inserire il voto corretto: '))
print('Voto accettato:', score)

Cicli annidati

Un ciclo che si trova dentro un altro ciclo si chiama ciclo annidato (o innestato). Il ciclo interno completa tutte le sue iterazioni prima che il ciclo esterno passi all'iterazione successiva.

Pensa a un orologio digitale: i secondi vanno da 0 a 59; solo dopo un giro completo dei secondi aumentano i minuti, e solo dopo un giro completo dei minuti aumentano le ore.

for ora in range(24):
    for minuto in range(60):
        for secondo in range(60):
            print(ora, ':', minuto, ':', secondo)

Questo programma stampa 24 × 60 × 60 = 86.400 righe: troppe per provarlo qui. Ecco una versione ridotta con 2 ore, 3 minuti e 3 secondi.

for ora in range(2):
    for minuto in range(3):
        for secondo in range(3):
            print(ora, ':', minuto, ':', secondo)

Il numero totale di iterazioni del ciclo più interno è il prodotto del numero di iterazioni di ogni ciclo: qui 2 × 3 × 3 = 18.

Cicli annidati e complessità

Due cicli annidati che scorrono ciascuno n elementi eseguono il corpo interno n × n volte. Si dice che hanno complessità O(n²) (quadratica): se n raddoppia, il tempo diventa circa quattro volte più grande.

Esempio: pattern triangolare

Di solito print va a capo dopo aver stampato. Con l'argomento end='' gli dici di non andare a capo: la stampa successiva continua sulla stessa riga. Un print() senza argomenti, invece, va soltanto a capo.

dim_base = 8
for r in range(dim_base):
    for c in range(r + 1):
        print('*', end='')
    print()

Il ciclo esterno sceglie la riga r; il ciclo interno stampa r + 1 asterischi su quella riga. Alla riga 0 c'è un asterisco, alla riga 7 ce ne sono 8. Dopo il ciclo interno, print() va a capo per iniziare la riga successiva.

Errori tipici

Errore Cosa succede Come si corregge
while x < 10 senza : SyntaxError: expected ':' aggiungi i due punti
nel while non si aggiorna la variabile della condizione ciclo infinito: il programma non termina modifica la variabile dentro il corpo, ad es. x += 1
for i in range(1, 10): per arrivare a 10 nessun errore, ma il 10 manca il limite superiore è escluso: range(1, 11)
accumulatore inizializzato dentro il ciclo nessun errore, ma il totale è sbagliato scrivi totale = 0 prima del ciclo
totale += x senza totale = 0 prima NameError: name 'totale' is not defined inizializza l'accumulatore
con la sentinella, nuovo input dimenticato in fondo al corpo ciclo infinito: il dato non cambia mai rileggi il dato come ultima istruzione del corpo
while voto < 0 and voto > 30: per la validazione la condizione non è mai vera: nessun controllo per "fuori dall'intervallo" usa or
print() dentro il ciclo interno invece che fuori va a capo dopo ogni asterisco allinea print() al ciclo interno, non al suo corpo

Esercizi

Esercizio 1 · Tabellina. Chiedi un numero intero e stampa la sua tabellina da 1 a 10, nella forma 7 x 3 = 21.

# Scrivi qui la tua soluzione
Soluzione
numero = int(input('Di quale numero vuoi la tabellina? '))
for i in range(1, 11):
    print(f'{numero} x {i} = {numero * i}')

Il limite superiore è 11 perché range lo esclude: così l'ultimo valore di i è 10.

Esercizio 2 · Numeri pari e dispari. Chiedi all'utente dei numeri interi positivi; il numero 0 fa da sentinella. Alla fine mostra quanti numeri pari e quanti dispari sono stati inseriti, e la loro somma. Suggerimento: usa due contatori e un accumulatore.

# Scrivi qui la tua soluzione
Soluzione
pari = 0
dispari = 0
somma = 0
numero = int(input('Inserisci un numero (0 per terminare): '))
while numero != 0:
    if numero % 2 == 0:
        pari += 1
    else:
        dispari += 1
    somma += numero
    numero = int(input('Inserisci un numero (0 per terminare): '))
print('Numeri pari:', pari)
print('Numeri dispari:', dispari)
print('Somma:', somma)

Prova con 4, 7, 10, 3 e poi 0: 2 pari, 2 dispari, somma 24.

Esercizio 3 · Media dei voti d'esame. Chiedi quanti esami ha sostenuto uno studente. Questo numero deve essere almeno 1: se non lo è, richiedilo. Poi chiedi il voto di ogni esame, accettando solo voti tra 18 e 30 (altrimenti mostra un errore e richiedi il voto). Alla fine mostra la media con due decimali e il voto più alto.

# Scrivi qui la tua soluzione
Soluzione
n_esami = int(input('Quanti esami hai sostenuto? '))
while n_esami < 1:
    print('ERRORE: il numero di esami deve essere almeno 1.')
    n_esami = int(input('Quanti esami hai sostenuto? '))

somma = 0
massimo = 0
for i in range(1, n_esami + 1):
    voto = int(input(f'Voto dell\'esame {i}: '))
    while voto < 18 or voto > 30:
        print('ERRORE: il voto deve essere compreso tra 18 e 30.')
        voto = int(input(f'Voto dell\'esame {i}: '))
    somma += voto
    if voto > massimo:
        massimo = voto

media = somma / n_esami
print(f'Media: {media:.2f}')
print('Voto più alto:', massimo)

Il ciclo di validazione del voto è annidato dentro il for. Prova con 0 esami (rifiutato), poi 3 esami con voti 25, 35 (rifiutato), 30, 28: la media è 27.67 e il voto più alto 30. La variabile massimo parte da 0, che è più basso di qualsiasi voto valido: così il primo voto la sostituisce sicuramente.

Verifica