Organizzare il percorso di un programma
Un programma deve stabilire che cosa viene eseguito dopo ogni operazione: un altro passo, uno fra percorsi alternativi oppure una nuova ripetizione. Sequenza, selezione e iterazione sono tre strumenti fondamentali per costruire questo controllo nei programmi imperativi strutturati.
Il capitolo Diagrammi di flusso e pseudocodice ne ha introdotto la rappresentazione. Qui approfondiamo come combinarli e come una diversa collocazione delle istruzioni cambi il risultato. Non riprendiamo la ricerca in un elenco: useremo esempi nuovi di calcolo, classificazione e accumulo. Tutti i dati sono illustrativi.
1. Sequenza: il risultato dipende dall’ordine
Una sequenza prescrive l’esecuzione di un blocco dopo il precedente, quando questo termina normalmente. Lo schema generale è:
ESEGUI A
ESEGUI B
ESEGUI C
A, B e C possono indicare operazioni elementari oppure interi blocchi. La sequenza definisce un ordine logico; non afferma che ogni riga duri lo stesso tempo o corrisponda a una sola istruzione del processore.
Supponiamo di applicare una riduzione del 10% a un importo di 100 unità e di aggiungere poi una quota fissa di 5. Ridurre prima e aggiungere dopo produce 95. Aggiungere prima e ridurre dopo produce invece 94,5: nel secondo caso abbiamo ridotto anche la quota fissa. Le operazioni sono le stesse, ma il procedimento è cambiato.
importo ← 100
importo ← importo × 0,90
importo ← importo + 5
MOSTRA importo
La freccia indica assegnazione. Le virgole indicano decimali nella notazione didattica adottata. La domanda decisiva è: quali risultati devono già essere disponibili prima di questo passo? Non tutte le operazioni sono scambiabili: l’indipendenza deve essere giustificata, non presunta.
2. Selezione: scegliere un’alternativa
La selezione binaria valuta una condizione e sceglie il blocco corrispondente:
SE condizione ALLORA
blocco A
ALTRIMENTI
blocco B
FINE SE
blocco comune
È lo schema generalmente espresso mediante
if/else
. Se il confronto restituisce vero viene eseguito A; se restituisce falso viene eseguito B. Il blocco comune segue il ramo scelto, purché questo termini normalmente. Un’uscita anticipata o un errore possono impedire di raggiungerlo.
Consideriamo una regola didattica: un testo con più di 80 caratteri riceve l’etichetta «lungo», altrimenti «breve». La lunghezza è già stata calcolata secondo una convenzione fissata. Un testo di esattamente 80 caratteri è breve, perché il confronto è
> 80
, non
≥ 80
. La scelta dell’operatore fa parte della regola, non della presentazione.
Quando manca
ALTRIMENTI
, il caso falso non esegue quel blocco e prosegue dopo
FINE SE
. Non significa che l’intero programma si arresti.
3. Due if non sono necessariamente un if/else
Immaginiamo tre fasce: almeno 100 punti assegna il livello «alto»; da 50 a meno di 100 il livello «medio»; sotto 50 il livello «base». Due condizioni indipendenti possono sovrascrivere una decisione precedente:
livello ← "base"
SE punti ≥ 100 ALLORA
livello ← "alto"
FINE SE
SE punti ≥ 50 ALLORA
livello ← "medio"
FINE SE
Con 120 punti entrambi i confronti sono veri: viene assegnato prima «alto» e poi «medio». Se vogliamo una sola fascia, dobbiamo rendere le alternative esclusive:
SE punti ≥ 100 ALLORA
livello ← "alto"
ALTRIMENTI SE punti ≥ 50 ALLORA
livello ← "medio"
ALTRIMENTI
livello ← "base"
FINE SE
La seconda condizione viene esaminata soltanto quando la prima è falsa. Anche l’ordine delle soglie conta: controllare prima «almeno 50» intercetterebbe anche 120, impedendo di raggiungere il ramo successivo. Due
if
separati sono invece appropriati quando due azioni devono poter avvenire entrambe, per esempio registrare un evento e, se richiesto da un’altra condizione, inviare una notifica.
4. Iterazione: stabilire che cosa viene ripetuto
Un ciclo delimita un corpo da rieseguire. Per scegliere lo schema conviene distinguere fra tre esigenze: attraversare elementi disponibili, continuare finché vale una condizione, compiere almeno un tentativo prima di decidere se ripetere.
PER OGNI elemento DI raccolta FAI
corpo
FINE PER
MENTRE condizione FAI
corpo
FINE MENTRE
RIPETI
corpo
FINCHÉ condizione_di_uscita
Qui
PER OGNI
visita nell’ordine gli elementi di una raccolta finita e immutata; una raccolta vuota produce zero esecuzioni del corpo.
MENTRE
valuta prima di ogni passaggio e può anch’esso non iniziare. Nel nostro
RIPETI … FINCHÉ
il controllo segue il corpo e una condizione vera conclude il ciclo: il corpo viene eseguito almeno una volta.
Queste sono convenzioni esplicite di pseudocodice. I nomi non bastano a tradurre un ciclo: in linguaggi con
do … while
, per esempio, una condizione finale vera richiede di continuare. Occorre preservare il significato della condizione, eventualmente negandola.
Un’attesa basata su un evento esterno richiede inoltre che l’informazione venga aggiornata. Controllare ripetutamente una copia invariata di un dato non equivale ad acquisire nuovi eventi. La terminazione dipende quindi dal corpo, dai dati e dalle ipotesi sull’ambiente.
5. Comporre le tre strutture: un accumulatore selettivo
Riceviamo una raccolta finita di masse strettamente positive, già espresse nella stessa unità. Vogliamo sommare soltanto quelle non superiori a 5 e contare quante vengono escluse. Il procedimento non acquisisce misure né controlla uno strumento fisico: elabora dati già disponibili.
totale ← 0
esclusi ← 0
PER OGNI massa DI masse FAI
SE massa ≤ 5 ALLORA
totale ← totale + massa
ALTRIMENTI
esclusi ← esclusi + 1
FINE SE
FINE PER
MOSTRA totale, esclusi
La sequenza iniziale prepara i due valori. Il ciclo presenta una massa alla volta. La selezione aggiorna uno dei due accumuli. L’uscita, posta dopo il ciclo, mostra il riepilogo una sola volta.
| Massa elaborata | Ramo | Totale dopo il passo | Esclusi dopo il passo |
|---|---|---|---|
| 2 | Ammessa | 2 | 0 |
| 7 | Esclusa | 2 | 1 |
| 4 | Ammessa | 6 | 1 |
Sui dati [2, 7, 4] otteniamo quindi totale 6 ed esclusi 1. Il valore 5 sarebbe ammesso. Con una raccolta vuota l’uscita resta totale 0 ed esclusi 0. Questi esiti discendono dal procedimento descritto; non sono risultati sperimentali.
6. Dentro o fuori: la posizione è parte del significato
Se spostiamo
totale ← 0
dentro il ciclo, prima della selezione, cancelliamo il contributo accumulato nei passaggi precedenti. Sull’esempio otteniamo infine 4 invece di 6. Se spostiamo
MOSTRA
dentro il ciclo, produciamo tre riepiloghi intermedi; con una raccolta vuota non ne produciamo alcuno.
Una selezione dentro un ciclo rivaluta la decisione a ogni elemento. Una selezione esterna può decidere se eseguire l’intero ciclo, ma non sostituisce automaticamente quella valutazione individuale. Analogamente, due cicli consecutivi completano prima una fase e poi l’altra; un ciclo annidato ripete il ciclo interno per ogni passaggio di quello esterno.
Se il ciclo esterno compie 3 passaggi e quello interno ne compie sempre 4, il corpo più interno viene eseguito 12 volte. Due cicli consecutivi di 3 e 4 passaggi eseguono invece i rispettivi corpi 7 volte complessive. È un conteggio di esecuzioni sotto ipotesi dichiarate, non un confronto automatico dei tempi reali.
7. Che cosa rimane uguale passando da un linguaggio all’altro
Le parole chiave e i delimitatori cambiano: un linguaggio usa rientri, un altro parentesi graffe, un altro parole di chiusura. Devono però restare riconoscibili l’ordine dei blocchi, la condizione di ciascuna alternativa, il corpo ripetuto e il punto in cui il controllo prosegue.
La documentazione ufficiale Python distingue infatti l’esecuzione condizionata di
if
, la ripetizione condizionata di
while
e l’attraversamento di un iterabile con
for
. È un riferimento concreto per confrontare questi schemi con un linguaggio eseguibile, senza assumere che tutti i linguaggi attribuiscano lo stesso significato a parole simili.
Per rileggere un procedimento basta partire da quattro domande: che cosa avviene una volta sola? Che cosa avviene soltanto in un caso? Che cosa avviene a ogni ripetizione? Che cosa viene eseguito dopo il blocco? Le risposte permettono di ricostruirne l’organizzazione prima di concentrarsi sulla sintassi.
8. Ambito e limiti del modello
Le tre strutture costituiscono una base della programmazione imperativa strutturata. Non sono un catalogo di ogni meccanismo esistente: ricorsione, eccezioni, concorrenza, eventi e paradigmi dichiarativi richiedono ulteriori strumenti descrittivi. Anche un programma che rimane attivo per servire richieste può essere intenzionalmente non terminante.
Qui assumiamo un singolo flusso di controllo, operazioni che terminano normalmente, dati disponibili e confronti ben definiti. L’organizzazione dell’esecuzione è distinta dal dettaglio fisico con cui un processore realizza le istruzioni. Le immagini associate sono illustrazioni concettuali generate: non documentano un apparato reale e non sostituiscono gli schemi testuali.
Il risultato didattico è saper comporre i blocchi conservandone il comportamento: ordinare le dipendenze, rendere esplicite le alternative e delimitare esattamente la ripetizione. Questa competenza prepara la lettura e la scrittura di programmi in linguaggi diversi.
