il metodo dei raffinamenti successivi top-down) è una ... · for var from espr1 to espr2 by espr3:...
TRANSCRIPT
Metodo dei raffinamenti successivi
Il metodo dei raffinamenti successivi (top-down) è una metodologia di programmazione basata sui seguenti principi:Divide-et-impera: il problema da risolvere è scomposto in
(sotto)problemi “più piccoli” che possono essere gestiti più facilmenteOgni sottoproblema verrà successivamente risolto con una codifica
diretta, oppure a esso si riapplicherà lo stessa tecnica (raffinamento)
Astrazione: il problema viene inizialmente affrontato nel suo complesso, studiandone i particolari in un secondo momento
È organizzato nelle seguenti fasi1. Analisi del problema2. Individuare un algoritmo3. Verifica e raffinamento (dell’algoritmo)4. Implementazione
Metodo dei raffinamenti successivi
1. Analisi del problema: capire bene il problema per convincersi che esiste una soluzioneinput : di quale informazione si disponeoutput: che cosa esattamente si vuole ottenere
spesso ci si convince di aver trovato una soluzione al problema ma questa risolve un problema più semplice di quello dato
Output?Input?
soluzione(algoritmo)
Metodo dei raffinamenti successivi
2. Individuare un algoritmo (G), ossia una successione finita di azioni che risolvono il problemaazione: una serie di operazioni che
quando effettuate producono un risultato previsto e ben determinato (determinismo)
si compiono in un certo intervallo di tempo (discretismo: finitezza dell’azione). Ogni azione modifica lo stato di uno o più oggetti
ci rendiamo conto che l’azione ha prodotto un risultato proprio dal cambiamento di stato dell’oggetto stesso.
istruzioni di programma (PG): comandi (in un linguaggio formale) interpretati dall’esecutore associando in modo univoco azioni
Metodo dei raffinamenti successivi
3. Verifica e raffinamento: verificare che la successione di azioni risolve veramente il problema …
… se la risposta è affermativa allora abbiamo un algoritmo che risolve il problema … per ogni azione dell’algoritmo:• se corrisponde ad una istruzione del linguaggio utilizzato (C), o
può essere facilmente tradotta in una breve successione di istruzioni in C vai al punto 4.
• altrimenti si assuma l’azione come un sottoproblema di quello originario e riapplicare per esso i punti 2. e 3.
… altrimenti tornare al punto 1.
Metodo dei raffinamenti successivi
4. Implementazione: traduci le azioni in istruzioni del programma• Il risultato di questo processo è la scrittura del programma
sorgente• Il programma sorgente è una tra le tante possibili implementazioni
(software) dell’algoritmo• Anche l’algoritmo è una delle possibili soluzioni ad un problema
dato
if condthen ...else ...
azione1
azione2
printf(...)
problemaG1
G2
P1G1P2
G1
......
Metodo dei raffinamenti successivi
Il metodo dei raffinamenti successivi può essere così schematizzato:
1. Attenta lettura iniziale del problema per convincersi che ammette soluzione.
2. Se esiste almeno una soluzione, descrivere in modo sommario le azioni da eseguire per poter determinare tale soluzione.
3. Se la successione di azioni porta alla soluzione allora possiamo raffinare ogni azione in altre azioni più dettagliate.
4. Il passo 3 termina quando il dettaglio è tale che ogni azione corrisponde ad una istruzione del linguaggio utilizzato (C nel nostro caso), o può essere facilmente tradotta in una breve successione di istruzioni in C.
Metodo dei raffinamenti successivi
Lo pseudocodice è un linguaggio artificiale e parzialmente formalizzato per esprimere le (macro)azioni di un algoritmo
istruzione Tipo Descrizione
var ß espr assegnazione Assegna il valore della espressione espr alla variabile var
if espr:<blocco1-istruzioni>
else:<blocco2-istruzioni>
selezione Esegue il <blocco1-istruzioni> se espr ha valore ‘vero’, altrimenti esegue <blocco2-istruzioni>
for var from espr1 to espr2by espr3:
<blocco-istruzioni>
iteratore Ripete il <blocco-istruzioni> per ogni valore di var che va da espr1a espr2 con passo espr3
while espr:<blocco-istruzioni>
iteratore Ripete il <blocco-istruzioni> finché il valore di espr è ‘vero’
do:<blocco-istruzioni>
while espr:
iteratore Ripete il <blocco-istruzioni> finché il valore di espr è ‘vero’ (esegue almeno una volta)
Metodo dei raffinamenti successivi
Lo pseudocodice è un linguaggio artificiale e parzialmente formalizzato per esprimere le (macro)azioni di un algoritmo
istruzione Tipo Descrizione
leggi(v1, ..., vn) input Legge n dati di input da tastiera e li assegna alle variabili v1, ..., vn
stampa(a1, ..., an) output Stampa n dati di output a schermo: a1, ..., an possono essere variabili o espressioni
foo(a1, ..., an) funzione Esegue una funzione sui parametri di input a1, ..., an e restituisce un valore (usato come espr negli altri costrutti)
function (v1, ..., vn):<blocco-istruzioni>
return espr:
definizione Definisce una funzione con argomenti v1, ..., vn e con valore di ritorno dato da espr
Metodo dei raffinamenti successivi
Problema:Dati un capitale iniziale C ed un tasso di interesse annuo T calcolare e
stampare l’interesse maturato (I) ed il capitale risultante (CR) dopo un anno, dopo due anni e dopo tre anni.
La stampa deve essere del tipo:Anno Capitale Tasso % Interesse Totale1 1.000 10 100 1.1002 1.100 10 110 1.2103 1.210 10 121 1.331
1. Analisi del problema: • I = C * T/100• CR = C + I = C * T = C (1 + T/100)• Dopo un anno il capitale iniziale C diventa il capitale risultante dell’anno
precedente (CR)
Metodo dei raffinamenti successivi
2. Individuare un algoritmo:Come partenza si individui una serie di macro azioni che risolvono il
problema
leggi(C,T)stampa(“Anno”,”Capitale”, ...)calcola_e_stampa_interesse_e_capitale_anno_1()calcola_e_stampa_interesse_e_capitale_anno_2()calcola_e_stampa_interesse_e_capitale_anno_3()
Algoritmo
Metodo dei raffinamenti successivi
3. Verifica e raffinamento:L’algoritmo risolve il problema posto se si conoscono le formule per il
calcolo dell’interesse maturato (I) e del capitale risultante (CR)
leggi(C,T)stampa(“Anno”,”Capitale”, ...)
calcola_e_stampa_interesse_e_capitale_anno_1()calcola_e_stampa_interesse_e_capitale_anno_2()calcola_e_stampa_interesse_e_capitale_anno_3()
Algoritmo
leggi(C,T)stampa(“Anno”,”Capitale”
, ...)I ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“1”,C,T,I,CR)C ß CRI ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“2”,C,T,I,CR)C ß CRI ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“3”,C,T,I,CR)
1o anno
2o anno
3o anno
Metodo dei raffinamenti successivi
3. Verifica e raffinamento:L’algoritmo risolve il problema posto se si conoscono le formule per il
calcolo dell’interesse maturato (I) e del capitale risultante (CR)
leggi(C,T)stampa(“Anno”,”Capitale”, ...)for t from 1 to 3:
Algoritmo
I ßcalcola_interesse(C,T)
CR ß calcola_totale(C,I)stampa(t,C,T,I,CR)C ß CR
leggi(C,T)stampa(“Anno”,”Capitale”,...)I ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“1”,C,T,I,CR)C ß CRI ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“2”,C,T,I,CR)C ß CRI ß calcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(“3”,C,T,I,CR)
Metodo dei raffinamenti successivi
3. Verifica e raffinamento:L’algoritmo risolve il problema posto se si conoscono le formule per il
calcolo dell’interesse maturato (I) e del capitale risultante (CR)
leggi(C,T)stampa(“Anno”,”Capitale”, ...)for t from 1 to 3:
I ß C ´ (T / 100)CR ß C + Istampa(t,C,T,I,CR)C ß CR
Algoritmo
leggi(C,T)stampa(“Anno”,”Capitale”, ...)for t from 1 to 3:
Algoritmo
I ßcalcola_interesse(C,T)CR ß calcola_totale(C,I)stampa(t,C,T,I,CR)C ß CR
Metodo dei raffinamenti successivi
4. implementazione:Ogni azione dell’algoritmo è
traducibile in istruzioni del linguaggio C
int main () {float Totale, Capitale, Tasso, Interesse;printf("Inserisci il Capita = ”); scanf(“%f”,&Capitale);printf("Tasso d'interesse(%) = "; scanf(“%f”,& Tasso);printf("Anno Capitale Tasso % Interesse Totale\n”);int i;for(i=1;i<=3; i++) {
Interesse=Capitale*Tasso/100;Totale=Capitale +Interesse;printf("%3d %10.2f %6.2f %10.2f %12.2f\n", i,
Capitale,Tasso,Interesse,Totale);Capitale=Totale;
}return 0;
}
leggi(C,T)stampa(“Anno”,”Capitale”, ...)for t from 1 to 3:
I ß C ´ (T / 100)CR ß C + Istampa(t,C,T,I,CR)C ß CR
Algoritmo
Metodo dei raffinamenti successivi
Problema: Calcolare la somma di due frazioni n1/d1, n2/d2 e ridurla ai minimi termini.
Primo raffinamento:leggi(n1,d1,n2,d2)calcola il numeratore, num, ed il denominatore, den, della sommariduci num e den ai minimi terministampa(num e den)
I quattro numeri in input non possono essere quattro interi qualsiasi perché un denominatore non può mai essere zero.
Quindi precondizioni: d1≠0 e d2≠0 Per ridurre num e den ai minimi termini dobbiamo prima trovare il massimo
comun divisore k, e successivamente effettuare le operazioni num¬num/k, den¬den/k.
Calcolare la somma di due frazioni n1/d1, n2/d2 e ridurla ai minimi termini.
Secondo raffinamento:leggi(n1,d1,n2,d2) (precondizione: d1≠0 e d2≠0)
den¬d1*d2num¬n1*d2+n2*d1
Calcola k, massimo comun divisore di num e dennum¬num/kden¬den/k
Terzo raffinamento:
leggi(n1,d1,n2,d2) (precondizione: d1>0,d2>0)den¬d1*d2num¬n1*d2+n2*d1n=numd=denwhile d!=0
temp=n%dn¬dd¬temp
k=n num¬num/kden¬den/k
Esercizi
Esercizi1. Scrivere un programma che, dato in input un intero positivo, calcola
separatamente le cifre del numero e le stampa separate da uno spazio– Esempio: 4523 à “4 5 2 3”– Suggerimento: si usi la divisione intera (\) e l’operatore resto (%)
2. Sulla base del programma precedente scrivere un programma che dato in input un intero positivo, dia come risultato il numero con le cifre in ordine inverso:– Esempio: 4523 à 3254
3. Due numeri interi a e b si dicono coprimi se MCD(a,b) = 1. Scrivere un programma che dati in input due numeri interi stampi a video ‘vero’ se sono coprimi, ‘falso’ altrimenti
4. Un numero si dice primo se è divisibile solo per 1 e per sé stesso.Scrivere un programma che, dato in input un numero intero positivo stampi a video ‘vero’ se è primo, ‘falso’ altrimenti
Esercizi
Esercizi:1. Un commerciante, per vendere di più un suo prodotto, il cui prezzo
è di 15,75 €, propone uno sconto del 12% per i clienti che ne acquistano più di 500 unità. Scrivere un programma che calcoli il ricavo effettivo per un acquisto di x unità.
2. Quali sono i cambiamenti da apportare al programma dell’esercizio precedente nel caso in cui il commerciante applichi uno sconto ulteriore del 10% quando un cliente acquisti almeno 1000 unità?
3. Scrivere un programma che chieda in input esattamente N numeri interi relativi compresi tra 100 e -100 e li stampi. Utilizzando tale programma scrivere un altro programma che stampila quantità di numeri positivi e negativi generati;il massimo ed il minimo dei valori generati;la differenza massima in valore assoluto tra ogni termine e il
precedente (non il primo)la differenza minima in valore assoluto tra ogni termine e il
precedente (non il primo)
Esercizi
Esercizi:4. Scrivere un programma che assegnato un numero intero positivo
stampi la somma dei suoi divisori ( escluso se stesso )5. Scrivere un programma che assegnato un numero intero positivo
stampi ‘vero’ se il numero è perfetto, ‘falso’ altrimentiun numero è perfetto se e solo la somma dei suoi divisori, escluso
se stesso, è uguale al numero stesso: 6=1+2+3 è perfetto 8=1+2+4 non lo è.
6. Per produrre una vernice sono necessari 10 grammi di un certo additivo per ogni chilo di prodotto fino a 10 chili e 5 grammi al chilo per i chili eccedenti. Scrivere un programma che fornisca la quantità di additivo necessaria in base al quantitativo di vernice richiesto
Astrazione procedurale
L’astrazione procedurale consiste nel descrivere tutti i sottoproblemi in cui un problema è descrivibile sostituendo poi a queste descrizioni una chiamata ad un sottoprogramma, non ancora scritto, il cui compito sarà quello di risolvere il corrispondente sottoproblema.
Il sottoprogramma dovrà ricevere tutta l’informazione (i dati) necessari per poter risolvere il problema.
• E’ necessario precisare quale è lo stato del sistema all’atto della chiamata del sottoprogramma (le precondizioni)
• quale sarà lo stato del sistema dopo l’esecuzione del sottoprogramma (le postcondizioni).
A questo punto si scrive lo pseudo codice per ognuno dei sottoprogrammi.
Naturalmente può accadere che qualcuno dei sottoprogrammi sia ancora così complesso da richiedere a sua volta la sua scomposizione in ulteriori sottoprogrammi.
Astrazione procedurale
Astrazione procedurale è una tecnica di sviluppo dei programmi in forma di procedure che realizza il metodo dei raffinamenti successivi• La scomposizione del problema in sottoproblemi più semplici si
traduce in una strutturazione del programma in un flusso di esecuzione di procedure• La procedura principale implementa una soluzione generale del problema
che nasconde i dettagli delle singole soluzioni parziali• Ogni procedura è un sottoprogramma che risolve un problema più
semplice (eventualmente usando la stessa tecnica di raffinamento)
T1in
T3
T2
T4 out
T0 (problema)
Astrazione procedurale
1. Decomposizione problema:• Il problema viene scomposto in un insieme di sottoproblemi (task)
• Si identifica un algoritmo per il problema principale• Ogni task utilizza dei dati (input) e genera nuovi o modifica dati
esistenti (output)
T1in
T3
T2
T4 out
T0 (problema)
G0
G4
G3
T0
T4T3
T2 G2
G1T1
Astrazione procedurale
2. Identificare dipendenze, cioè l’ordine di risoluzione dei task• Individuare per ogni task i dati
di input e da quali task sono elaborati• Definire i dati di output (prodotti o modificati)
dal task che saranno usati da altri task• L’ordine di risoluzione non è
necessariamente unico
T1in
T3
T2
T4 out
T0 (problema)
T1
T2
T3
T4
T2,T3
T4,out
T4
outT2,T3
T1
T1
in
ord
ine
di
riso
luzi
one
Astrazione procedurale
3. Individuare precondizioni (postcondizioni)• che i dati di input (output) devono soddisfare affinché ogni task
operi correttamente su di essi (e generi dati utilizzabili da altri task) • tipo dei dati e intervallo di valori• invarianza di dati passati in input al task e
emessi in output
Astrazione procedurale
4. Implementazione algoritmo• Traduzione degli algoritmi individuati in sequenze di istruzioni
(programma e sottoprogrammi) in pseudocodice o nel linguaggio di programmazione (C)• Definire per ogni task un nome (del sottoprogramma corrispondente)• Scrittura del programma (principale) sostituendo l’esecuzione di ogni
task con una chiamata al nome del sottoprogramma corrispondente• Scrittura a parte dei sottoprogrammi corrispondenti ad ogni task
• Può accadere che qualcuno dei sottoprogrammi sia ancora così complesso da richiedere a sua volta la sua scomposizione in ulteriori sottoprogrammi
• ritorna al punto 1
G0
G4
G3
T0
T4T3
T2 G2
G1T1
leggi(in)a ß task1(in)b ß task2(a)c ß task3(a)d ß task4(b,c)stampa(b,d)
Task0Task1Task2Task3
function task4(p1, p2):<blocco-istruzioni>
return ... :
Task4
C funzioni à chiamata
Esistono due tipi di sottoprogrammi: le procedure e le funzioniLe funzioni restituiscono sempre un solo valoreLe procedure non restituiscono valori
In C esistono solo funzioniuna procedura è una funzione che ritorna il valore void
Una chiamata di funzione ha la seguente sintassi:
nome(parametri);
• è a tutti gli effetti una istruzione del programma• Il valore dell’istruzione è il valore ritornato dalla funzione• parametri è la lista dei parametri (eventualmente nulla) cioè dei
dati passati dal programma chiamante alla funzione
Esempio: k = mcd(a,b); calcola il massimo comun divisore di due numeri interi
C funzioni à chiamata
La chiamata di una funzione non di tipo void può essere inserita come:
• operando in qualsiasi espressione• parametro nella chiamata di un'altra funzione
• il compilatore controlla che il tipo della funzione sia ammissibile
• la chiamata viene eseguita con precedenza rispetto alle altre operazioni e al suo posto viene sostituito il valore di ritorno restituito dalla funzione
Se la funzione è di tipo void (procedura), la chiamata non può essere inserita in una espressione, ma deve assumere la forma di un'istruzione a se stante
Esempio: k = (mcd(a,b) * n) / 2;
foo(mcd(a,b), d);
C funzioni à definizione
La definizione di funzione ha la seguente sintassi: tipo nome(argomenti) {
istruzione1; ...
}
Dove:• tipo è il tipo del valore di ritorno della funzione, detto anche tipo della
funzione;• se la funzione non ha valore di ritorno (procedura), bisogna specificare void
• nome: è l'identificatore della funzione• segue le regole generali di specifica degli identificatori
• argomenti è la lista di argomenti (dati) passati dal programma chiamante • vanno specificati insieme al loro tipo (come nelle dichiarazioni delle
variabili) e, se più d'uno, separati da virgole;• se non vi sono argomenti, si può specificare void (o, più
comodamente, non scrivere nulla fra le parentesi).
C funzioni à definizione
La definizione di funzione ha la seguente sintassi: tipo nome(argomenti) {
istruzione1; ...
}
Esempio: char MiaFunz(int dato, float valore)
la funzione di nome MiaFunz riceve dal programma chiamante gli argomenti:
dato (di tipo int), e valore (di tipo float),
e ritorna un risultato di tipo char.
C funzioni à return
Nel codice di implementazione di una funzione l'istruzione di ritorno al programma chiamante é:
return espressione;
• il valore calcolato dell'espressione viene restituito al programma chiamante come valore di ritorno della funzione
• se il tipo non è quello dichiarato della funzione, il compilatore segnala un errore, oppure, quando può, esegue una conversione implicita (con warning se c'é pericolo di loss of data)
• Non é necessario che tale istruzione sia l'ultima (ma è bene farlo)• Non é necessario che ve ne sia una sola
• dipende dalla presenza delle istruzioni di controllo, che possono interrompere l'esecuzione della funzione in punti diversi
• Se la funzione non ha valore di ritorno (procedura), bisogna specificare return; (da solo).
• può essere omessa quando il punto di ritorno coincide con la fine della funzione
C funzioni à esempio
Definiamo una funzione per l’algoritmo (di Euclide) che calcola il MCD di due interi a e b:
Si tratta di una vera funzione perché gli argomenti sono usati solo per il calcolo del MCD
function mcd(a,b):r ß modulo(a,b)while (r > 0):
a ß bb ß rr ß modulo(a,b)
return b:
pseudocodice
int mcd (int a, int b) {
int r;r = a % b;while (r>0) {
a = b;b = r;r = a % b;
}return b;
}
codice C