Lezione 08 · Programmi, algoritmi ed esecutori; diagrammi di flusso¶
Cosa impari
- che cosa sono un problema, una sua istanza, i dati di input e di output;
- la differenza tra algoritmo, esecutore e programma;
- le istruzioni e le tre strutture di controllo (sequenza, selezione, iterazione);
- la differenza tra sequenza statica e sequenze dinamiche di esecuzione;
- a disegnare e leggere un diagramma di flusso e a tradurlo in Python.
Prima di scrivere un programma bisogna sapere come si risolve il problema. Questo "come" è l'algoritmo. In questa lezione impari a descriverlo in modo preciso, anche con un disegno: il diagramma di flusso. È lo stesso lavoro che fai all'esame quando, prima di scrivere il codice, ragioni sui passi da fare.
Problemi e istanze¶
Uno degli scopi principali dell'informatica è risolvere problemi con i calcolatori.
Un problema è una classe di domande dello stesso tipo, a cui si risponde con lo stesso metodo. Ogni domanda specifica della classe è un'istanza del problema.
- Problema: "qual è la somma Z di due numeri X e Y?"
- Istanza: "qual è la somma di 4 e 7?"
X e Y sono le variabili di ingresso (dati di input): ogni combinazione dei loro valori genera un'istanza diversa. Z è la variabile di uscita (dato di output): il suo valore dipende dall'istanza.
| X | Y | Istanza | Z |
|---|---|---|---|
| 4 | 7 | qual è la somma di 4 e 7? | 11 |
| 3 | 5 | qual è la somma di 3 e 5? | 8 |
Non tutti i problemi sono uguali¶
| Problema | Osservazione |
|---|---|
| preparare una torta alla frutta | si conosce il risultato, ma non la ricetta: la descrizione è troppo generica |
| risolvere un'equazione di secondo grado | il procedimento è noto con chiarezza |
| trovare il massimo tra tre numeri | ambiguo: si vuole il valore del massimo o la sua posizione? |
| invitare degli amici | ci sono più soluzioni (posta, SMS, e-mail, messaggi): bisogna scegliere la più conveniente |
| trovare le tracce del passaggio di extraterrestri | non ammette soluzione |
Quindi la descrizione di un problema di solito non dice come risolverlo, a volte è imprecisa o ambigua, e per alcuni problemi una soluzione non esiste: si chiamano problemi non calcolabili. La teoria della calcolabilità studia quali problemi si possono risolvere con un procedimento automatico.
Calcolabile ma non trattabile: la Torre di Hanoi¶
Ci sono tre paletti e n dischi di grandezza decrescente. Bisogna spostare tutti i dischi da un paletto a un altro, un disco alla volta, senza mai mettere un disco sopra uno più piccolo.
Il problema ha una soluzione, ma richiede almeno 2ⁿ − 1 mosse. Con n = 64 dischi e una mossa al secondo servono 2⁶⁴ − 1 secondi: circa 585 miliardi di anni. Il problema è calcolabile ma non trattabile: la soluzione esiste, ma richiede un tempo inaccettabile.
n = int(input('Numero di dischi: '))
mosse = 2 ** n - 1
anni = mosse / (365.25 * 24 * 3600) # una mossa al secondo
print('Mosse necessarie:', mosse)
print(f'Tempo con una mossa al secondo: {anni:.1f} anni')
Prova con 10, 30 e 64.
Algoritmo, esecutore, programma¶
Un algoritmo è una sequenza finita di passi che porta alla realizzazione di un compito: un insieme finito di istruzioni che, eseguite in un ordine stabilito, portano alla soluzione di un problema. Gli aspetti importanti sono:
- i passi sono in numero finito;
- vanno eseguiti in sequenza, in un ordine preciso;
- elaborano i dati di input;
- producono i dati di output, cioè la soluzione.
L'esecutore è chi esegue l'algoritmo. Un algoritmo dipende sia dal compito sia dall'esecutore: l'esecutore riesce nel suo compito se e solo se comprende ed esegue tutti i passi.
flowchart LR
I[/"Input"/] --> E["Esecutore + Algoritmo"]
E --> O[/"Output"/]
| Esecutore | Algoritmo | Input | Output |
|---|---|---|---|
| cuoco | ricetta | latte, uova, zucchero | torta |
| matematico | formula delle equazioni di 2° grado | coefficienti a, b, c | radici |
Un programma è un algoritmo scritto in un linguaggio di programmazione, cioè un linguaggio comprensibile all'esecutore. Il linguaggio descrive, senza ambiguità, tutte e sole le operazioni che l'esecutore sa fare.
Un calcolatore è un esecutore particolare: non ha capacità decisionale, compie solo le azioni previste. Il suo processore è un automa, una macchina che esegue algoritmi: a partire dai dati iniziali produce in uscita i risultati.
Descrivere un algoritmo¶
Per risolvere un problema:
- si capisce se il problema ammette soluzioni;
- se sì, si trova un metodo risolutivo (l'algoritmo);
- si esprime il metodo in un linguaggio comprensibile all'esecutore.
In un algoritmo ci sono due tipi di frasi:
- le istruzioni, che descrivono le operazioni da fare;
- le strutture di controllo, che dicono in quale ordine eseguirle.
Istruzioni elementari e non elementari¶
- Elementari: l'esecutore le capisce e le sa eseguire direttamente (es.
somma = a + b). - Non elementari: l'esecutore non le conosce, quindi bisogna fornirgli anche la loro specifica, che le scompone in istruzioni elementari. Permettono di scrivere l'algoritmo in modo più compatto (es. "calcola m = max(a, b)", che poi va spiegata con un confronto).
Le tre strutture di controllo¶
| Struttura | Che cosa fa | In Python |
|---|---|---|
| sequenza | le azioni si eseguono una dopo l'altra | istruzioni scritte una sotto l'altra |
| selezione | un'azione si esegue solo se vale una condizione | if, if-else, if-elif-else |
| iterazione | un'azione si ripete un numero prestabilito di volte o finché vale una condizione | for, while |
Caratteristiche delle operazioni¶
Ogni operazione di un algoritmo deve avere quattro caratteristiche:
- finitezza: termina in un tempo finito;
- descrivibilità: produce effetti descrivibili, ad esempio confrontando lo stato degli oggetti prima e dopo;
- riproducibilità: nelle stesse condizioni iniziali produce sempre lo stesso effetto;
- comprensibilità: è espressa in una forma che l'esecutore capisce.
Sequenza statica e sequenze dinamiche¶
Quando l'esecutore lavora, compie una serie di azioni una dopo l'altra: un processo sequenziale. L'elenco delle istruzioni effettivamente eseguite, nell'ordine di esecuzione, si chiama sequenza di esecuzione (o sequenza dinamica).
La sequenza statica è invece l'ordine in cui le istruzioni sono scritte nell'algoritmo.
- Una sola sequenza statica può dare origine a molte sequenze dinamiche, a seconda dei dati.
- Esempio: nelle equazioni di 2° grado, se il discriminante è negativo il programma segue una strada (nessuna radice reale), altrimenti un'altra (calcolo delle radici).
- Se l'algoritmo contiene un ciclo, il numero di sequenze dinamiche può essere infinito. Nel solitario del carcerato si possono eliminare le carte al primo tentativo, al secondo, dopo n tentativi… oppure mai: il processo non termina.
Studiare quante e quali sequenze dinamiche ci sono serve a confrontare soluzioni diverse (quale è più veloce?) e a capire se un algoritmo termina.
I diagrammi di flusso¶
Un diagramma di flusso (flow chart) rappresenta un algoritmo con un disegno. Ogni azione è un blocco; le frecce indicano l'ordine di esecuzione. Ogni blocco ha un ramo in ingresso e uno o più rami in uscita.
| Blocco | Forma | Uso |
|---|---|---|
| Inizio / Fine | ovale (rettangolo arrotondato) | punto di partenza e di arrivo |
| Elaborazione | rettangolo | calcoli e assegnamenti, es. somma = a + b |
| Input / Output | parallelogramma | leggere dati (Leggi a, b) o stamparli (Stampa somma) |
| Selezione a due vie | rombo | una condizione (predicato) con due uscite: SI e NO |
Esempio: somma di due numeri¶
flowchart TD
S(["Inizio"]) --> L[/"Leggi a, b"/]
L --> C["somma = a + b"]
C --> P[/"Stampa somma"/]
P --> F(["Fine"])
- Il parallelogramma Leggi a, b legge due numeri e li salva nelle variabili di ingresso
aeb. - Il rettangolo fa il calcolo e assegna il risultato alla variabile di uscita
somma. - Il parallelogramma Stampa somma mostra il risultato.
Esempio: massimo tra due numeri¶
Prima versione, con un'istruzione non elementare: "calcola m = max(a, b)". Se l'esecutore non sa cos'è il massimo, bisogna scomporla con un rombo:
flowchart TD
S(["Inizio"]) --> L[/"Leggi a, b"/]
L --> D{"a > b ?"}
D -- SI --> A["m = a"]
D -- NO --> B["m = b"]
A --> P[/"Stampa m"/]
B --> P
P --> F(["Fine"])
a = float(input('a = '))
b = float(input('b = '))
if a > b:
m = a
else:
m = b
print('Il massimo è', m)
Esempio: massimo tra tre numeri¶
flowchart TD
S(["Inizio"]) --> L[/"Leggi a, b, c"/]
L --> D1{"a > b ?"}
D1 -- SI --> D2{"a > c ?"}
D1 -- NO --> D3{"b > c ?"}
D2 -- SI --> PA[/"Stampa a"/]
D2 -- NO --> PC1[/"Stampa c"/]
D3 -- SI --> PB[/"Stampa b"/]
D3 -- NO --> PC2[/"Stampa c"/]
PA --> F(["Fine"])
PC1 --> F
PB --> F
PC2 --> F
Ci sono 4 sequenze dinamiche diverse (una per ogni blocco "Stampa"), ma la sequenza statica è una sola. In Python i rombi diventano if annidati:
a = float(input('a = '))
b = float(input('b = '))
c = float(input('c = '))
if a > b:
if a > c:
print('Il massimo è', a)
else:
print('Il massimo è', c)
else:
if b > c:
print('Il massimo è', b)
else:
print('Il massimo è', c)
I cicli nei diagrammi di flusso¶
Spesso alcune istruzioni vanno ripetute. Nel diagramma un ciclo è una freccia che torna indietro.
Ciclo while¶
Gli elementi del ciclo while sono:
- l'inizializzazione delle variabili usate nella condizione: la prima volta che si controlla la condizione, deve avere un valore sensato;
- la condizione del ciclo, un test che decide se entrare (o restare) nel ciclo;
- il corpo del ciclo, le istruzioni da ripetere. Nel corpo ci deve essere un'istruzione di modifica che, prima o poi, rende falsa la condizione. Altrimenti il ciclo è infinito.
Il test si fa prima del corpo: se la condizione è subito falsa, il corpo non viene mai eseguito.
Ciclo do-while¶
Nel ciclo do-while (o repeat-until) il test si fa dopo il corpo: il corpo viene eseguito almeno una volta.
Do-while in Python
Python non ha il do-while. Si ottiene con while True: e un break alla fine del corpo, quando la condizione di uscita è vera.
Esempio: stampa dei numeri da 1 a 10¶
Si usa una variabile contatore i che parte da 1, si stampa e si incrementa dopo ogni stampa, e ci si ferma quando supera 10.
Soluzione con while: il test è in alto, prima della stampa.
flowchart TD
S(["Inizio"]) --> I["n = 10<br>i = 1"]
I --> D{"i ≤ n ?"}
D -- SI --> P[/"Stampa i"/]
P --> INC["i = i + 1"]
INC --> D
D -- NO --> F(["Fine"])
Soluzione con do-while: il test è in basso, dopo la stampa.
flowchart TD
S(["Inizio"]) --> I["n = 10<br>i = 1"]
I --> P[/"Stampa i"/]
P --> INC["i = i + 1"]
INC --> D{"i ≤ n ?"}
D -- SI --> P
D -- NO --> F(["Fine"])
n = 10
# Versione while: il test è prima del corpo
i = 1
while i <= n:
print(i, end=' ')
i = i + 1
print()
# Versione do-while: il test è dopo il corpo
i = 1
while True:
print(i, end=' ')
i = i + 1
if not (i <= n):
break
print()
Esempio: stampa dei numeri pari fino a −1¶
Leggere un numero; se è −1 terminare; se è pari stamparlo; poi (pari o dispari) tornare a leggere.
flowchart TD
S(["Inizio"]) --> L[/"Leggi N"/]
L --> D1{"N = -1 ?"}
D1 -- SI --> F(["Fine"])
D1 -- NO --> D2{"N è pari ?"}
D2 -- SI --> P[/"Stampa N"/]
D2 -- NO --> L
P --> L
N = int(input('Numero (-1 per finire): '))
while N != -1:
if N % 2 == 0:
print(N, 'è pari')
N = int(input('Numero (-1 per finire): '))
print('Fine')
Esempio: massimo tra n numeri¶
Si parte con max = −∞ (un valore più piccolo di qualunque numero) e un indice i = 1. Si confronta ogni aᵢ con max e, se è più grande, si aggiorna max.
flowchart TD
S(["Inizio"]) --> L[/"Leggi a1, ..., an"/]
L --> I["max = -∞<br>i = 1"]
I --> D1{"ai > max ?"}
D1 -- SI --> A["max = ai"]
D1 -- NO --> INC["i = i + 1"]
A --> INC
INC --> D2{"i > n ?"}
D2 -- NO --> D1
D2 -- SI --> P[/"Stampa max"/]
P --> F(["Fine"])
numeri = [12, 45, 7, 45, 30, 2] # i valori a1, ..., an
n = len(numeri)
massimo = float('-inf') # meno infinito
i = 0 # in Python le posizioni partono da 0
while i < n:
if numeri[i] > massimo:
massimo = numeri[i]
i = i + 1
print('Il massimo è', massimo)
Esempio: sconto sul prezzo¶
Dati i prezzi di 3 prodotti, se il totale è minore di 500 € si applica uno sconto del 15%, altrimenti del 20%. Si stampa il prezzo finale.
flowchart TD
S(["Inizio"]) --> I["sconto = 0.15"]
I --> L[/"Leggi p1, p2, p3"/]
L --> T["totale = p1 + p2 + p3"]
T --> D{"totale ≥ 500 ?"}
D -- SI --> S2["sconto = 0.2"]
D -- NO --> C["totale = totale * (1 - sconto)"]
S2 --> C
C --> P[/"Stampa totale"/]
P --> F(["Fine"])
Il trucco: si parte dallo sconto "normale" (15%) e lo si cambia solo se serve.
sconto = 0.15
p1 = float(input('Prezzo 1: '))
p2 = float(input('Prezzo 2: '))
p3 = float(input('Prezzo 3: '))
totale = p1 + p2 + p3
if totale >= 500:
sconto = 0.2
totale = totale * (1 - sconto)
print(f'Prezzo finale: {totale:.2f} euro')
Esempio: calcolo della media¶
Leggere valori non negativi finché l'utente inserisce 0; poi calcolare e stampare la media. Servono un contatore n e un accumulatore tot.
flowchart TD
S(["Inizio"]) --> I["n = 0<br>tot = 0"]
I --> L[/"Leggi x"/]
L --> D{"x = 0 ?"}
D -- NO --> A["tot = tot + x<br>n = n + 1"]
A --> L
D -- SI --> M["m = tot / n"]
M --> P[/"Stampa m"/]
P --> F(["Fine"])
Attenzione alla divisione per zero
Se il primo valore inserito è 0, n vale 0 e tot / n dà errore. Nel programma conviene controllare n > 0 prima di dividere.
n = 0
tot = 0
x = float(input('Valore (0 per finire): '))
while x != 0:
tot = tot + x
n = n + 1
x = float(input('Valore (0 per finire): '))
if n > 0:
m = tot / n
print('Media:', m)
else:
print('Nessun valore inserito')
Nota che la lettura compare due volte in Python: una prima del ciclo (inizializzazione) e una alla fine del corpo (modifica). Nel diagramma invece la freccia torna sullo stesso blocco "Leggi x".
Esempio: prodotto tramite somme¶
Calcolare x · y (con x ≥ 0 e y ≥ 0) usando solo somme: si somma x a sé stesso y volte.
flowchart TD
S(["Inizio"]) --> L[/"Leggi x, y"/]
L --> I["p = 0"]
I --> D1{"x = 0 ?"}
D1 -- SI --> P[/"Stampa p"/]
D1 -- NO --> D2{"y = 0 ?"}
D2 -- SI --> P
D2 -- NO --> A["p = p + x<br>y = y - 1"]
A --> D2
P --> F(["Fine"])
x = int(input('x (>= 0): '))
y = int(input('y (>= 0): '))
p = 0
if x != 0:
while y != 0:
p = p + x
y = y - 1
print('Prodotto:', p)
Errori frequenti negli esercizi¶
| Errore | Cosa succede | Come si corregge |
|---|---|---|
| usare il rettangolo per leggere o stampare | diagramma scorretto | input e output vanno nel parallelogramma |
| rombo con una sola uscita, o uscite senza etichetta | non si capisce che cosa succede | ogni rombo ha due uscite: SI e NO |
ciclo senza istruzione di modifica (es. manca i = i + 1) |
ciclo infinito | nel corpo deve cambiare la variabile della condizione |
| contatore non inizializzato prima del ciclo | il primo test non ha senso; in Python NameError |
inizializza (es. i = 1, tot = 0) prima del ciclo |
in Python, lettura solo prima del while |
ciclo infinito: il valore non cambia mai | rileggi il valore alla fine del corpo del ciclo |
| confondere while e do-while | con dati "limite" il corpo viene eseguito una volta di troppo (o di meno) | while: test prima; do-while: test dopo, corpo almeno una volta |
Esercizi¶
Esercizio 1 · Tipo di triangolo. Date le lunghezze a, b, c dei lati di un triangolo, stampa se è equilatero (tre lati uguali), isoscele (due lati uguali) o scaleno (tutti diversi). Disegna prima il diagramma di flusso, poi traducilo in Python.
Soluzione
flowchart TD
S(["Inizio"]) --> L[/"Leggi a, b, c"/]
L --> D1{"a = b ?"}
D1 -- SI --> D2{"b = c ?"}
D2 -- SI --> E[/"Stampa equilatero"/]
D2 -- NO --> I1[/"Stampa isoscele"/]
D1 -- NO --> D3{"a = c ?"}
D3 -- SI --> I2[/"Stampa isoscele"/]
D3 -- NO --> D4{"b = c ?"}
D4 -- SI --> I3[/"Stampa isoscele"/]
D4 -- NO --> SC[/"Stampa scaleno"/]
E --> F(["Fine"])
I1 --> F
I2 --> F
I3 --> F
SC --> F
Esercizio 2 · Massimo tra dieci numeri. Leggi 10 numeri, uno alla volta, e stampa il più grande. Suggerimento: usa un contatore i da 1 a 10. Al primo numero (i = 1) il massimo è proprio quel numero; poi aggiorni il massimo solo se il nuovo numero è più grande.
Soluzione
flowchart TD
S(["Inizio"]) --> I["n = 10<br>i = 1"]
I --> D1{"i ≤ n ?"}
D1 -- SI --> L[/"Leggi x"/]
L --> D2{"i = 1 OR x > max ?"}
D2 -- SI --> A["max = x"]
D2 -- NO --> INC["i = i + 1"]
A --> INC
INC --> D1
D1 -- NO --> P[/"Stampa max"/]
P --> F(["Fine"])
n = 10
i = 1
while i <= n:
x = float(input('Numero: '))
if i == 1 or x > massimo:
massimo = x
i = i + 1
print('Il massimo è', massimo)
Grazie alla valutazione a corto circuito, quando i == 1 è vero Python non valuta x > massimo (che darebbe errore, perché massimo non esiste ancora).
Esercizio 3 · Somma dei pari con sentinella. Leggi numeri interi finché l'utente inserisce −1. Alla fine stampa quanti numeri pari sono stati inseriti e la loro somma. Disegna il diagramma e traducilo in Python. Con l'input 4, 7, 10, 3, −1 il programma deve stampare 2 pari, somma 14.
Soluzione
flowchart TD
S(["Inizio"]) --> I["conta = 0<br>somma = 0"]
I --> L[/"Leggi N"/]
L --> D1{"N = -1 ?"}
D1 -- NO --> D2{"N è pari ?"}
D2 -- SI --> A["conta = conta + 1<br>somma = somma + N"]
D2 -- NO --> L
A --> L
D1 -- SI --> P[/"Stampa conta, somma"/]
P --> F(["Fine"])