La programmazione
Gli elaboratori sono macchine capaci
di effettuare un numero limitato di
operazioni molto semplici, ma a
grande velocità.
Combinando insieme sequenze di
operazioni semplici è possibile
realizzare
funzioni
comunque
complesse, che possono soddisfare le
esigenze più disparate.
È
possibile
quindi
realizzare
programmi per l'elaborazione di testi,
per la gestione di grosse banche dati,
per il controllo di robot e di processi,
e così via.
1
Condizione fondamentale affinché si
possa risolvere un problema mediante
l'elaboratore è che esso sia stato
definito con precisione.
Non si può infatti dire alla macchina
semplicemente:
“Calcola la criticità di un reattore”
“Evadi gli ordini di un'azienda”
“Aggiorna i conti correnti di una
banca”
“Traduci in lingua inglese”
2
Per le applicazioni di tipo scientifico,
si dovranno definire i problemi in
termini di sistemi di equazioni, di
formule da calcolare che a loro volta
dovranno essere poste in termini
elementari (i soli comprensibili
all'elaboratore).
Per le elaborazioni aziendali si dovrà
redigere un processo di ragionamento
logico assai dettagliato, comparazioni
di codici, letture di tabelle, confronti
fra quantità, ecc.
3
Quindi per risolvere un determinato
problema occorre esaminarlo nei
dettagli più minuti.
La sequenza di passi che porta alla
soluzione di un problema è detto
algoritmo.
La
sequenza
di
operazioni,
incorporate nella logica della
macchina,
utilizzate
per
lo
svolgimento di una determinata linea
risolutiva, si dice programma.
4
L'esperienza quotidiana suggerisce
numerosi esempi di algoritmi: una
ricetta da cucina, le istruzioni per
l'uso di un televisore, le indicazioni
per un lavoro a maglia, le regole per
calcolare
la
somma
o
la
moltiplicazione di due numeri.
Le operazioni che un algoritmo può
prescrivere possono essere di natura
assai
differente,
ma
devono
possedere,
per
poter
essere
considerate tali, delle caratteristiche
ben precise.
5
 ogni operazione deve avere termine
entro un intervallo di tempo finito
dall'inizio della sua esecuzione.
“Cavalcare un centauro” o “calcolare
le cifre decimali di ” non possono
essere considerate operazioni: la
prima, infatti, non è eseguibile affatto
e la seconda non può avere termine in
un tempo finito.
6
 ogni operazione deve produrre, se
eseguita, un effetto osservabile che
possa essere descritto.
Ad
esempio,
l'esecuzione
dell'operazione di “spostare il tavolo”
produce un effetto che può essere
descritto specificando la disposizione
dei mobili nella stanza prima e dopo
lo spostamento.
Invece, “pensare” o “pensare al
numero 5” non possono essere
considerate operazioni poiché non è
affatto chiaro come il loro effetto
possa essere “osservato” e descritto.
7
 ogni operazione deve produrre lo
stesso effetto ogni volta che venga
eseguita a partire dalle stesse
condizioni iniziali (determinismo).
Così se X vale 5 e Y vale 10, tutte le
volte che eseguiamo la somma di X e
Y con quei valori iniziali, il risultato
dovrà essere sempre 15.
L'esecuzione di un algoritmo da parte
di un “esecutore” - uomo o macchina
- si traduce in una successione di
operazioni che vengono effettuate nel
tempo evocando un “processo
sequenziale”.
8
Per “processo sequenziale” s’intende
una serie di eventi che occorrono uno
dopo l'altro, ognuno con un inizio e
una fine bene identificabile.
Talvolta il processo è fisso, cioè è
sempre lo stesso ad ogni diversa
esecuzione.
Esempio: algoritmo per calcolare
l'importo di una fattura:
9
 cerca l'aliquota IVA sulla tabella;
 moltiplica l'importo netto per
l'aliquota trovata;
 somma il risultato all'importo netto.
Questo algoritmo è composto da tre
istruzioni, che devono essere eseguite
in sequenza.
L'elencazione, una dopo l'altra, di
tutte le istruzioni eseguite, nell'ordine
di esecuzione è detta sequenza di
esecuzione dell'algoritmo.
10
Più frequentemente lo stesso
algoritmo può evocare più processi
sequenziali differenti, a seconda delle
condizioni iniziali.
Esempio: il precedente algoritmo, se
è richiesto do dover considerare la
possibilità che la merce in esame non
sia soggetta a IVA.
11
 SE la merce da fatturare è soggetta
a IVA ALLORA
 Cerca la corretta aliquota IVA
sulla tabella e moltiplica
l'importo per l'aliquota trovata.
Somma il risultato all'importo
netto.
 ALTRIMENTI
 Tieni conto solo dell'importo di
partenza.
12
In questo caso, il processo evocato
non è fisso, ma dipende dai dati da
elaborare, in particolare dal tipo di
merce da fatturare: l'algoritmo
descrive un insieme costituito da due
sequenze di esecuzione diverse.
Esistono situazioni, ancora più
complesse, in cui il numero di
sequenze d'esecuzione descritte da
uno stesso algoritmo non solo non è
noto a priori, ma addirittura può
essere infinito.
13
Ad
esempio,
l'algoritmo per
telefonata:
consideriamo
effettuare una
1 solleva il ricevitore;
2 componi il numero;
3 SE qualcuno risponde ALLORA
3.1conduci la conversazione
4 ALTRIMENTI
4.1deponi il ricevitore e
4.2RIPETI
L'INTERO
PROCEDIMENTO.
L'algoritmo
sequenze
corrispondenti
descrive
infinite
d'esecuzione,
al fatto che la
14
telefonata riesca al 1, 2, 3, ...
tentativo.
Si potrà avere un processo ciclico che
non
avrà
mai
termine
se
l'interlocutore non risponde mai al
telefono!
Si può dire che un algoritmo è un
testo in grado di descrivere un
insieme di sequenze di esecuzioni.
Diversi sono i costrutti linguistici che
possono essere utilizzati allo scopo.
Ad esempio, l'algoritmo della
telefonata potrebbe essere:
15
1. Solleva il ricevitore
2. Componi il numero
3. SE qualcuno risponde ALLORA
SALTA al PUNTO 6
4. Deponi il ricevitore
5. TORNA AL PUNTO 1
6. Conduci la conversazione
Negli algoritmi fino ad ora visti sono
indicate due classi fondamentali di
costrutti:
 costrutti
che
l'esecuzione
di
operazioni (come
ricevitore”);
16
prescrivono
determinate
“deponi il
 costrutti che indicano l'ordine di
esecuzione delle operazioni (come
“torna al punto 1”).
I primi sono detti istruzioni o
comandi, i secondi schemi di
controllo o istruzioni di controllo.
È stato dimostrato (Bohm - Jacopini)
che qualsiasi algoritmo può essere
realizzato utilizzando due soli
costrutti di controllo:
 costrutto di decisione binaria
 costrutto di ripetizione (o loop)
17
Il costrutto di decisione binaria è
anche detto costrutto IF - THEN ELSE
(SE
ALLORA
ALTRIMENTI) e segue lo schema:
IF condizione THEN
sequenza A
ELSE
sequenza B
SE la condizione è vera ALLORA
esegui la sequenza A, ALTRIMENTI
esegui la sequenza B.
18
Graficamente si rappresenta così:
INIZIO
FALSO
VERO
C
B
A
FINE
SE la condizione C è vera allora
esegui la sequenza A
ALTRIMENTI
esegui la sequenza B
19
Nei casi in cui manca la parte ELSE
il costrutto condizionale diventa
semplicemente:
IF condizione THEN
sequenza A
SE la condizione è vera ALLORA
esegui la sequenza A (ovvero "esegui
la sequenza A se e solo se la
condizione è vera").
20
Il meccanismo di ripetizione, detto
anche meccanismo DO-WHILE, si
può manifestare in due forme diverse.
La prima è detta
(FINCHÉ-ESEGUI),
rappresentazione è:
WHILE-DO
la
cui
WHILE condizione DO
sequenza istruzioni
Continua ad eseguire la sequenza di
istruzioni finché la condizione è vera.
21
Se già alla prima verifica la
condizione risulta falsa, la sequenza
di istruzioni non verrà mai eseguita.
Graficamente:
INIZIO
FALSO
C
FINE
VERO
A
Finché la condizione C è vera
esegui la sequenza A
22
La seconda è detta REPEAT-UNTIL
(RIPETI-FINCHÉ) ed è simmetrica
alla precedente:
REPEAT
sequenza istruzioni
UNTIL condizione
Qui il blocco di istruzioni precede il
controllo: pertanto verrà sempre
eseguito almeno una volta anche se la
condizione risulta falsa alla prima
verifica.
23
Graficamente:
INIZIO
A
C
VERO
FALSO
FINE
Esegui
la sequenza A
finché la condizione C è vera
24
Una derivazione interessante del
primo dei due costrutti è il ciclo a
contatore, che ha la forma:
FOR variabile FROM valore_iniz
TO valore_fin DO
sequenza istruzioni
25
A variabile si attribuiscono tutti i
valori compresi tra valore_iniz e
valore_fin e, per ogni attribuzione, si
esegue la sequenza di istruzioni.
Se all'inizio valore_iniz è maggiore
di valore_fin, le istruzioni non
vengono mai eseguite.
L’istruzione o comando è un
costrutto di un certo linguaggio, ad
esempio l'italiano, che prescrive
l'esecuzione di una operazione.
26
Le frasi:
 sposta il tavolo;
 mischia le carte;
 calcola l'IVA del totale;
sono istruzioni in italiano, che
prescrivono l'esecuzione di tre
operazioni diverse.
Finora non è stata fatta alcuna ipotesi
sulla forma grammaticale di una
istruzione.
27
Si richiede solamente che esista un
insieme,
arbitrario
ma
ben
specificato, di regole grammaticali
che permettano di
stabilire con
certezza se una data sequenza di
caratteri sia da considerarsi o meno
una istruzione: questo insieme di
regole è detto sintassi.
L'esistenza
di
un'istruzione
presuppone il fatto che sia eseguita.
Affinché ciò risulti possibile, è
necessario
che
l'esecutore
dell'algoritmo in cui essa appare ne
conosca non solo la sintassi ma anche
il significato (semantica).
28
Ad esempio, il segno  nell’espressione a  b assume il significato di
somma algebrica se a e b sono dei
numeri, ma potrebbe assumere il
significato di concatenamento di
stringhe se a e b sono (sequenze di)
caratteri.
Si dicono elementari quelle istruzioni
il cui significato possa ritenersi noto
al loro esecutore, non elementari le
altre.
Il fatto che una istruzione sia
considerata elementare oppure no
dipende dal particolare esecutore a
cui essa è rivolta.
29
Ad esempio l'istruzione:
“COMPONI IL NUMERO”
sarà considerata elementare soltanto
se l'esecutore ha esperienza in fatto di
telefonate.
30
In caso contrario, il suo significato
dovrà essere definito in termini di
operazioni più semplici. Ad esempio:
SE la chiamata è extraurbana
ALLORA
componi il prefisso
POI:
componi la prima cifra;
componi la seconda cifra;
componi la terza cifra;
......................
31
Potremmo ancora specificare che
“COMPONI IL PREFISSO”
significa:
componi la prima cifra del prefisso;
componi la seconda cifra del prefisso;
..................
e così via.
32
Dal precedente esempio si desume
che, quando si deve risolvere un
problema e si studia un algoritmo a
tale scopo, non necessariamente si
pensa ad una successione di istruzioni
elementari per l'esecutore.
In generale si scompone il problema
principale in un certo numero di
problemi più semplici, ognuno dei
quali viene ulteriormente scomposto
per ottenere una sequenza di
operazioni atte alla soluzione.
33
L'unione di tutte le sequenze di
operazioni così ottenute formerà la
soluzione del problema principale ed
in definitiva sarà proprio l'algoritmo
risolutivo.
Supponiamo di voler specificare un
algoritmo per effettuare il prodotto di
due numeri interi col metodo delle
addizioni successive. L'algoritmo
potrebbe
essere
costituito
semplicemente dal seguente testo:
 Si sommi il moltiplicando a se
stesso un numero di volte uguale al
valore del moltiplicatore.
34
Specifichiamo
con
maggiore
dettaglio le operazioni richieste
evitando i costrutti ambigui. Ad
esempio, potremmo scrivere:
 Si sommi il moltiplicando a se
stesso, e si decrementi di uno il
valore del moltiplicatore.
 Si sommi ancora il moltiplicando al
valore ottenuto dalla precedente
somma, e si decrementi di nuovo il
valore ottenuto dalla precedente
sottrazione.
 Si ripeta il procedimento fino a che,
per decrementi successivi, non si
raggiunga il valore uno.
35
Per evitare ambiguità, si è stati
costretti ad usare le espressioni:
“valore ottenuto dalla precedente
somma”
e
“valore ottenuto dalla precedente
sottrazione”
per specificare, di volta in volta, di
quale valore si sta parlando.
36
Per distinguere tali valori si ricorre a
simboli o identificatori per riferirsi
senza ambiguità, di volta in volta, al
valore desiderato.
Ad esempio:
1 Si chiami M il valore del
moltiplicando ed N il valore del
moltiplicatore e sia M1 il risultato
(inizialmente zero).
2 Si ripetano le seguenti operazioni
fino a che il valore di N non
diventi uguale a 0:
37
2.1 si
sommi il valore del
moltiplicando M al valore di M1
e si chiami il risultato ancora
M1;
2.2si sottragga 1 dal valore di N, e
si chiami il risultato ancora N.
Alla fine il valore di M1 è il risultato.
I simboli M, N e M1 sono detti
identificatori
di
variabili
(è
conveniente che siano di tipo
mnemonico).
Si può concludere che per costruire
un algoritmo (e quindi un
programma) sono necessari solo tre
38
blocchi di base, caratterizzati
dall’avere un solo punto d’inizio e un
solo punto di fine:
1)blocco di elaborazione
2)blocco di ripetizione (o loop)
3)blocco di decisione binaria.
La particolare forma utilizzata per
descrivere un algoritmo, in cui si
puntualizzano
le
operazioni
effettuate, i costrutti di controllo, le
variabili
implicate,
è
detta
diagramma di flusso.
39
Se negli anni '60 si preferiva per i
diagrammi di flusso la forma grafica,
dagli anni '70 in poi si è prevalsa la
forma testuale, utilizzando un
liguaggio in gergo detto Pascal-like
(quello degli esempi), in cui i
costrutti di controllo sono espressi in
modo non ambiguo.
Impostato il diagramma di flusso, è
necessario procedere alla sua verifica
mediante una prova logica per
controllare se la linea operativa
tracciata è esatta oppure contiene
errori.
40
La
prova
logica
consiste
nell'ipotizzare dei dati campione,
calcolare su di essi le operazioni del
problema, quindi eseguire passo
passo le funzioni espresse dal
diagramma di flusso fino ad arrivare
allo stop.
Alla fine si confrontano i risultati
calcolati con altri metodi e quelli a
cui si è pervenuti col diagramma. Se
sono uguali l’algoritmo (con buona
probabilità) è esatto.
41
La prova logica si suddivide nelle
quattro fasi seguenti:
42
1. Stesura dei dati campione: deve
essere fatta tenendo presente tutte
le particolarità che i dati del
problema possono presentare.
2. Calcolo dei risultati per altra via:
si calcolano i risultati per vie
diverse da quelle del diagramma di
flusso.
3. Prova logica effettiva: si parte dalla
posizione di inizio (start) e si
elabora,
sui
dati
ipotizzati,
eseguendo una a una le operazioni
indicate dal diagramma, finché non
si raggiunge lo stop.
43
4. Controllo dei risultati: si verifica la
corrispondenza fra i risultati della
prova logica effettiva e quelli
ottenuti per via diversa.
I diagrammi di flusso possono
presentarsi ad un livello generale, a
grandi linee, o ad un livello
particolare, dove sono descritte le
operazioni elementari.
Il diagramma a grandi linee tende ad
evidenziare il “grosso del problema”,
dando risalto alle operazioni di
maggiore importanza.
44
Il diagramma dettagliato è più utile al
momento
della
stesura
del
programma e successivamente per il
test e la manutenzione del
programma stesso.
Analizziamo gli elementi comuni ai
principali
linguaggi
di
programmazione (Pascal, Fortran, C,
Basic, ecc.), procedendo mediante
esempi.
Tutte le informazioni (numeri,
caratteri o altro) che devono essere
elaborate da un programma devono
essere poste in un “contenitore”.
45
Tutti i linguaggi consentono di
definire diversi tipi di “contenitori” e
di aggregarli insieme in “strutture”.
L'insieme
di
“contenitori”
e
“strutture è detto “base-dati” del
problema.
Il riferimento a questi “contenitori”
(o “strutture”) avviene mediante
nomi o “etichette” scelti dal
programmatore e che assumono il
nome di “identificatori”.
46
Col termine di “identificatore” si
intende una sequenza di uno o più
caratteri utilizzati per indicare
un'entità ben definita.
Per reperire o deporre informazioni
stabilite dalla procedura di calcolo si
fa
riferimento
a
contenitori
identificati da una etichetta.
Il simbolo:
Am
significa appunto “riponi m nel
contenitore etichettato A”.
47
Inizializzare un contenitore vuol dire
introdurre per la prima volta un
elemento.
Realizziamo come esempio un
diagramma di flusso per la ricerca del
massimo fra quattro numeri.
Siano N1, N2, N3, N4 e max gli
identificatori dei quattro numeri e del
massimo che cerchiamo.
48
Ipotizziamo dapprima che il numero
più grande sia N1 e lo mettiamo in
max;
confrontiamo ora il contenuto di max
con N2: se N2 è maggiore allora
trascriviamo in max tale elemento, se
invece N2 è minore non alteriamo il
contenuto di max.
Ripetiamo il procedimento con N3 e
N4.
49
1 Leggi N1, N2, N3, N4
2 max  N1
3 IF N2 > max THEN
3.1max  N2
4 IF N3 > max THEN
4.1max  N3
5 IF N4 > max THEN
5.1max  N4
6 stampa “Il massimo è” max
Si osservi come il precedente non sia
un processo ottimizzato in quanto è
valido solo per 4 elementi. Se
vogliamo trovare il massimo fra 5
elementi occorrerà aggiungere un
altro confronto.
50
Quando
si
devono
effettuare
operazioni su elementi simili, cioè
della
stessa
specie,
conviene
“strutturare” i dati, ad esempio in un
“vettore” e utilizzare etichette con
indici.
Si individua ogni elemento con una
etichetta, ad esempio N, più un indice
che lo distingue dagli altri dello
stesso tipo.
Normalmente si rappresenteranno
così:
N[1],
N[2]
o,
più
51
genericamente, N[I], dove I funge da
etichetta dell'indice.
52
Graficamente:
N[1]
N[I] con I=5
N[8]
Usando questa notazione possiamo
modificare il precedente diagramma
di flusso.
53
1 Per I da 1 a 4
1.1leggi N[I]
2 max  N[1]
3 Per I che va da 2 a 4
3.1IF N[I] > max THEN
3.1.1 max  N[I]
4 stampa “Il massimo è” max
Il due diagrammi differiscono tra loro
sostanzialmente: il primo procede
direttamente dall'inizio alla fine; il
secondo itera perché contiene un
ciclo o loop.
54
Meglio ancora generalizzare l’algoritmo rispetto al numero di elementi
trattati. Si può ottenere in due modi:
 fissando come parametro il numero
di elementi, cioè come costante
simbolica
definita
in
testa
all’algoritmo;
 definendolo come variabile il cui
valore è caricato da un dispositivo
di
ingresso
all’inizio
del
programma.
55
L’esempio precedente si modifica
così (il ciclo a contatore è stato
cambiato in ciclo while per mostrarne
l’equivalenza):
1 Leggi num_dati
2 indice  1
3 While indice  num_dati
3.1leggi N[indice]
3.2incrementa indice
4 max  N[1]
5 indice  2
6 While indice  num_dati
6.1IF N[indice] > max THEN
6.1.1 max  N[indice]
6.2incrementa indice
7 stampa “Il massimo è” max
56
Altro esempio: diagramma di flusso
che effettua la somma di N numeri.
Sia somma il risultato della somma
(somma è quindi un accumulatore) e
num_dati il numero di elementi su
cui operare.
Poiché il contenuto iniziale di un
contenitore è impredicibile, occorre
inizializzare a zero il contenitore
somma, in modo da ottenere alla fine
la somma corretta.
57
1 Leggi num_dati
2 indice  1
3 While indice  num_dati
3.1leggi N[indice]
3.2incrementa indice
4 somma  0
5 indice  1
6 While indice  num_dati
6.1somma  somma + N[indice]
6.2incrementa indice
7 stampa “La somma è” somma
58
Caratteristiche
programma
di
un
buon
- La modularità: conviene spezzare il
problema da risolvere in tanti
problemi parziali.
La combinazione delle soluzioni dei
sotto-problemi permette di ottenere la
soluzione finale.
In questa maniera si semplifica anche
il lavoro di prova e correzione del
programma nel suo complesso.
59
Scrivere
programmi
modulari
significa anche poter riutilizzare, con
minime variazioni, i singoli moduli in
altri programmi.
- La documentazione, ossia la
descrizione
dettagliata
delle
caratteristiche del programma e del
suo modo di operare.
In genere si divide in due parti.
Nella prima detta esterna si suole
dare tutte le informazioni necessarie
per ottenere le migliori prestazioni
dal programma.
60
Contiene le modalità d'uso del
programma, il significato dei
messaggi
emessi
durante
l'esecuzione, altre caratteristiche
proprie quali l'occupazione di
memoria,
i
sottoprogrammi
richiamati, ecc.
La seconda parte, detta interna, è la
descrizione degli algoritmi usati, i
diagrammi di flusso e i commenti.
Tutti i linguaggi di programmazione
prevedono la possibilità di inserire
commenti all’interno dei programmi.
61
- La leggibilità, frutto di una oculata
scelta degli algoritmi risolutivi, di
una efficace suddivisione in moduli e
di un uso appropriato delle strutture
del linguaggio.
Caratteristiche più difficilmente
definibili e qualificabili sono
l'efficienza e la portabilità.
La prima è facilmente intuibile: è
efficiente
un
programma
che
raggiunge lo scopo per il quale è stato
realizzato, è veloce, occupa poco
spazio di memoria, permette una
buona interazione con l'utente.
62
Per portabilità s'intende la possibilità
di far "girare" il programma su
piattaforme differenti.
Per ottenere la portabilità, conviene
usare quanto più possibile linguaggi
standard di alto livello, limitando al
minimo quelle forme non-standard
ammesse dal linguaggio (estensioni o
dialetti).
L'attività più onerosa è il collaudo:
nella stesura di un programma si
commettono involontariamente molti
errori: la prova e la correzione
permetteranno di scoprirli.
63
Questa fase è detta debugging (da to
debug, “spulciare”).
Correggere un programma è tanto più
semplice quanto più esso è
documentato e il flusso delle
istruzioni è continuo, quasi armonico.
L'esperienza dimostra che il requisito
dell'efficienza in senso stretto è
richiesto in un numero di applicazioni
molto limitato.
È invece rilevante il tempo impiegato
nella
manutenzione
e
nell'aggiornamento dei programmi.
64
Il requisito della leggibilità non deve
pertanto essere mai sacrificato.
Conviene partire dal presupposto che
i programmi devono essere compresi
prima da altri uomini e solo
successivamente dalle macchine!
65
Algoritmi
Che cosa s’intende comunemente per
algoritmo ?
“Un algoritmo è costituito da un
insieme finito di passi distinti e non
ambigui che, eseguiti a partire da
assegnate
condizioni
iniziali,
producono l’output corrispondente e
terminano in un tempo finito”.
La definizione precedente implica
aver identificato uno o più “passi”
che si è in grado di eseguire:
66
occorre disporre di una serie di
operazioni (elementari o complesse)
e di un “esecutore” capace di
realizzare tali operazioni.
La descrizione di un algoritmo
dipende dunque dall’insieme di
operazioni disponibili.
Esiste un metodo per inventare
algoritmi?
No, ma esistono strategie che
possono venire in soccorso (metodo
euristico).
67
Sulla falsariga di quanto ha proposto
il matematico Polya per la
dimostrazione di teoremi, si possono
suggerire i seguenti punti salienti:
 scomposizione
dei
problemi
complessi in problemi più semplici
(si suppone che siano noti un certo
numero di problemi elementari di
cui si conosce la soluzione);
 focalizzazione
dell’attenzione:
riconoscimento di uno o più “passi”
che risultano risolutivi (la soluzione
può essere ottenuta, ad esempio,
ripetendo più volte lo stesso passo);
68
 minimizzazione delle differenze tra
“descrizione attuale della soluzione”
ed “obiettivo”; si noti che ciò
implica individuare e tentare di
risolvere dapprima il problema – o
l’aspetto del problema – più
rilevante, rimandando a fasi
successive il raffinamento della
soluzione;
 utilizzo delle analogie: se il
problema che si sta affrontando può
essere ricondotto alla forma di un
problema che si è già risolto, si può
riutilizzare lo stesso tipo di
soluzione.
69
Per la soluzione degli esercizi
proposti si suppone di disporre delle
seguenti operazioni:
 somma / sottrazione / prodotto tra
numeri interi o numeri reali: il
risultato è rispettivamente un
numero intero o un reale
 divisione tra numeri reali:
risultato è un numero reale
il
 divisione tra numeri interi: il
risultato è la parte intera del
quoziente (quota)
70
 modulo tra numeri interi: il risultato
è il resto della divisione tra numeri
interi
 confronto per >, , =, <,  tra
numeri interi e numeri reali: il
risultato è un valore logico, VERO o
FALSO
 and (“e insieme”) / or (“oppure”) /
not (negazione logica) tra valori
logici: il risultato è ancora di tipo
logico
71
Come si vede, le operazioni ammesse
presuppongono
l’esistenza
di
particolari tipi di dato: nel nostro
caso, esistono contenitori, variabili o
costanti, per numeri interi e per
numeri reali.
Per semplicità, si può ipotizzare che
esistano come elementari le seguenti
altre azioni su questi tipi di variabili:


Leggi
dato,
operazione
di
introduzione di un valore nel
contenitore dato da un periferico
(per esempio, dalla tastiera);
Stampa dato, operazione di
visualizzazione del contenuto di
72
dato
(per
videoterminale).
esempio,
sul
Alcune altre operazioni su dati
strutturati (accesso ad elementi di un
vettore, ecc.) verranno introdotte in
seguito.
73
Esercizio 1
Realizzare il diagramma di flusso per
il calcolo dell’area geometrica di un
triangolo.
1 Leggi la base
2 Leggi l’altezza
3 Calcola base  altezza / 2, risultato
in area_triangolo
4 Fine.
74
Esercizio 2
Realizzare un algoritmo per il calcolo
della tabellina pitagorica di un
numero.
Occorre identificare
significativo:
un
passo
1 Calcola numero  fattore, risultato
in prodotto
75
Stabilisco quante volte devo ripetere
l’operazione:
1 Con fattore che assume valori da
1 a 10
1.1Calcola numero  fattore,
risultato in prodotto
Completo con i dettagli:
1 Leggi fattore
2 Con fattore che assume valori da
1 a 10
2.1Calcola numero  fattore,
risultato in prodotto
2.2Visualizza prodotto
3 Fine
76
Esercizio 3
Realizzare un algoritmo per il calcolo
della tabellina pitagorica dei numeri
da 1 a 10.
Occorre identificare
significativo:
un
passo
1 Calcola la tabellina di numero
(operazione già nota)
77
Stabilisco quante volte devo ripetere
l’operazione:
1 Con numero che assume valori da
1 a 10
1.1Calcola la tabellina di numero
L’algoritmo completo è:
1 Con numero che assume valori da
1 a 10
1.1Con fattore che assume valori
da 1 a 10
1.1.1 Calcola
numero

fattore, risultato in prodotto
1.1.2 Visualizza prodotto
2 Fine
78
Esercizio 4
Trasformare il seguente algoritmo
non strutturato in un equivalente
algoritmo strutturato:
1 si pone 1 in num_val e 0 in
accum
2 con indice che va da 1 a 100 si
esegue
2.1si legge un dato intero in
varint
2.2se varint < 0 si va a 3.
2.3si somma varint ad accum
2.4si incrementa num_val
3 si decrementa num_val
4 si visualizza accum
5 fine.
79
L’algoritmo contiene un ciclo a
contatore basato su indice, ma dal
ciclo si esce anche se si verifica
l’evento “varint < 0”.
Occorre pertanto trasformare il ciclo
a contatore in un ciclo basato su
evento (ciclo WHILE o ciclo
REPEAT-UNTIL).
Si esce dal ciclo se sono stati trattati
tutti i dati (test su indice) oppure
se il dato letto in varint è negativo.
Nell’adottare un ciclo basato su
evento occorre invece determinare la
condizione per continuare il ciclo:
80
si permane nel ciclo finché non sono
stati trattati tutti i dati e allo stesso
tempo varint è positivo o nullo.
1 si pone 1 in num_val e 0 in
accum
2 si pone indice a 0, varint a 0
3 finché (indice < 100) e
insieme (varint  0) si esegue
3.1si legge un dato intero in
varint
3.2se varint  0
3.2.1si somma varint ad
accum
3.2.2si incrementa num_val
3.3si incrementa indice
4 si decrementa num_val
5 si visualizza accum
81
6 fine.
All’interno del ciclo si è inserito un
test su varint per ottenere un
comportamento equivalente a quello
dell’algoritmo specificato dal testo
dell’esercizio.
82
Esercizio 5
Realizzare un algoritmo per calcolare
la media aritmetica di una sequenza
forniti da un dispositivo di input.
Analisi
nel
matematica:
dominio
della
 la media si calcola effettuando la
somma di tutti i numeri e dividendo
per la quantità di numeri;
 la divisione è lecita se il divisore
non è nullo.
83
Identifico un passo significativo:
1 Leggi numero
2 Calcola numero + accumulatore,
risultato in accumulatore
3 Incrementa quanti_numeri
Determino quante volte effettuare il
passo significativo, tengo conto di
dover effettuare le inizializzazioni e
della condizione per effettuare
correttamente la divisione.
84
L’algoritmo è:
1 Inizializza
accumulatore
e
quanti_numeri con il valore 0
2 Finché ci sono numeri
2.1Leggi numero
2.2Calcola
numero
+
accumulatore, risultato in
accumulatore
2.3Incrementa quanti_numeri
3 Se quanti_numeri > 0
3.1Calcola
accumulatore
/
quanti_numeri, risultato in
media
4 Altrimenti
4.1Visualizza
“media
non
calcolabile”
5 Fine.
85
NB: nel realizzare l'algoritmo, si è
ipotizzato di poter testare la
disponibilità
di
altri
numeri
provenienti dal dispositivo di input.
Se ciò non risultasse vero, la frase 2Finché ci sono numeri - dovrebbe
essere ulteriormente specificata.
86
Esercizio 6
Realizzare un algoritmo per il calcolo
del massimo comun divisore
(M.C.D.) di due numeri interi
positivi.
Analisi nel dominio della
matematica:
Euclide ha già proposto un algoritmo
efficiente, che sfrutta la seguente
proprietà: se a > b, il M.C.D. tra a e b
è anche il M.C.D. tra a-b e b;
il procedimento può essere reiterato e
termina quando, in seguito a
87
sottrazioni successive, si ottengono
due numeri uguali.
88
Algoritmo:
 Leggi dato_a e dato_b
 Finché dato_a  dato_b
 Se dato_a > dato_b
 calcola dato_a - dato_b,
risultato in dato_a
 altrimenti
 calcola dato_b - dato_a,
risultato in dato_b
 Assegna a
massimo_comun_divisore il valore
di dato_a
 Fine.
89
II Soluzione
Si può utilizzare il procedimento di
scomposizione in fattori primi.
Si ipotizza di poter disporre di un
vettore vett_primi, di lunghezza
adeguata, che contenga i numeri
primi almeno fino al più piccolo dei
numeri considerati.
1
2
3
5
7
11
ecc.
90
Analizzo il problema nel dominio
della matematica:
Il M.C.D. è costituito dal prodotto dei
numeri primi per i quali i due numeri
sono pienamente divisibili.
Occorre tener presente che un
numero primo può contribuire al
M.C.D. con molteplicità superiore ad
1.
91
Identifico un passo significativo:
 cerca fattore comune di dato_a e
dato_b in vett_primi, determina se
trovato e restituisce il valore in
fatt_comun
 Se trovato è vero
 moltiplica il valore precedente
di
massimo_comun_denominatore
per fatt_comun
 riduci dato_a e dato_b di
fatt_comun
92
Nota: il primo passo (“cerca...”) può
essere ritenuto un’azione primitiva,
se esiste un “modulo” che lo attua.
Lo si trasforma in procedura o
funzione, in cui dato_a e dato_b sono
parametri d’ingresso e trovato e
fatt_comun sono parametri d’uscita.
Occorre determinare quante volte
occorre ripetere il passo significativo,
e successivamente le inizializzazioni.
Riscrivendo l’algoritmo in modo più
formale, si ha:
93
Main
 Legge dato_a e dato_b
 Pone 1 in massimo_comun_denominatore
 Pone VERO in trovato
 Finché trovato è VERO
 Cerca_fattore_comune(ingressi:
dato_a, dato_b; uscite: trovato,
fatt_comun)
 Se trovato è VERO
 Calcola fatt_comun 
massimo_comun_denominat
ore, risultato in
massimo_comun_denominatore
 calcola dato_a / fatt_comun,
risultato in dato_a
94
 calcola dato_b / fatt_comun,
risultato in dato_b
 Visualizza
massimo_comun_denomi-natore
 Fine.
Passo alla soluzione del
sottoproblema.
Devo verificare se un generico
elemento di vett_primi divide
pienamente dato_a e dato_b.
95
Il passo significativo è:
 Se ((dato_a MOD
vett_primi[indice]) = 0) e insieme
((dato_b MOD vett_primi[indice])
= 0)
 Pone VERO in trovato
 Pone vett_primi[indice] in
fatt_comun
 altrimenti
 Incrementa indice
96
Determino quante volte devo
eseguire questo passo:
 Smetto di cercare appena trovo il
fattore comune.
 Il fattore cercato non può essere
superiore al minimo tra dato_a e
dato_b.
Determino le inizializzazioni: devo
ignorare il primo numero primo, cioè
1: pertanto l’indice di scansione del
vettore vett_primi deve iniziare da 2.
97
L’algoritmo completo è:
Cerca_fattore_comune(ingressi:
dato_a, dato_b; uscite: trovato,
fatt_comun)
 Pone 2 in indice
 Se dato_a < dato_b
 pone dato_a in minimo_ab
 altrimenti
 pone dato_b in minimo_ab
98
 Finché (NO trovato) e insieme
(vett_primi[indice]  minimo_ab)
 Se ((dato_a MOD
vett_primi[indice]) = 0) e
insieme ((dato_b MOD
vett_primi[indice]) = 0)
 Pone VERO in trovato
 Pone vett_primi[indice] in
fatt_comun
 altrimenti
 Incrementa indice
 Fine.
99
Conclusioni
Ai criteri generali per creare buoni
algoritmi si possono aggiungere i
seguenti suggerimenti:




analizzare il problema nel suo
dominio specifico, cercando di
evidenziare tutte le specifiche
esplicite ma anche quelle implicite;
creare un esempio, il più generale
possibile;
creare esempi dei casi limiti;
nella realizzazione dell’algoritmo,
cercare di trattare tutti i dati allo
stesso modo, evitando di trattare a
parte il primo dato;
100



prendere come riferimento i dati
dell’esempio tipico e partire non
dall’inizio, né dalla fine, ma dal
centro, immaginando che i dati
precedenti siano stati già trattati:
individuare
così
il
passo
fondamentale;
mettere a posto le inizializzazioni
affinché
l’algoritmo
operi
correttamente alla partenza;
verificare che con questo vengano
trattati correttamente anche i casi
limite.
Se ciò non accade, tentare di
effettuare piccole modifiche per far
rientrare anche i casi limiti: se le
modifiche sono onerose, valutare
101
l’opportunità
di
rigettare
completamente l’algo-ritmo e di
crearne un altro su nuove basi.
Data la stretta dipendenza tra azioni e
base dati, anche quest’ultima
dovrebbe essere valutata con occhio
critico.
102