1k likes | 1.19k Views
m, se m=n. MCD(m, n) =. MCD(m-n, n), se m>n. MCD(m, n-m), se m<n. RICORSIONE & ITERAZIONE. Riconsideriamo l’esempio del Massimo Comun Divisore visto tempo addietro:. RICORSIONE & ITERAZIONE. Questo esempio era stato trasposto nella funzione seguente: int mcd(int m, int n){
E N D
m, se m=n MCD(m, n) = MCD(m-n, n), se m>n MCD(m, n-m), se m<n RICORSIONE & ITERAZIONE Riconsideriamo l’esempio del Massimo Comun Divisore visto tempo addietro:
RICORSIONE & ITERAZIONE Questo esempio era stato trasposto nella funzione seguente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); } L’esempio era particolare perché il risultato veniva sintetizzato in avanti, anziché all’in-dietro come nei processi ricorsivi.
RICORSIONE & ITERAZIONE • Ogni processo computazionale che computi“in avanti”, per accumulo, costituisce una ITERAZIONEossia è unprocesso computazionale iterativo. • Ogni soluzione sintatticamente ricorsivache dia luogo a unprocesso computazio-nale iterativo costituisce una ricorsione solo apparente:una ricorsione tail.
RICORSIONE & ITERAZIONE La caratteristica fondamentale di un processo computazionale ITERATIVO è che a ogni passo è disponibile un risultato parziale • dopo k passi, si ha a disposizione il risultato parziale relativo al caso k • questo non è vero nei processi computazionali ricorsivi,in cui nulla è disponibile finché non si è disgregato il problema fino al caso elementare.
IL RAGIONAMENTO ITERATIVO • Si basa sulla disponibilità di una variabile, detta accumulatore, destinata a esprimere in ogni istante la soluzione corrente • Si imposta identificando quell’operazione di modifica dell’accumulatoreche lo porta a esprimere, dal valore relativo al passo k, il valore relativo al passo k+1.
ESEMPIO: CALCOLO DEL FATTORIALE Definizione: n! = 1 * 2 * 3 *… * n Detto vk = 1 * 2 * 3 *… * k: 1! = v1 = 1 (k+1)! = vk+1 = (k+1) * vkper k1 n! = vn per k=n
ESEMPIO: CALCOLO DEL FATTORIALE int fact(int n){ return factIter(n,1,1); } int factIter(int n, int v, int k){ return (k==n) ? v : factIter(n, (k+1)*v, k+1); }
INVARIANTI Un invariante di programmaè una relazione sempre vera in un dato punto del program- ma. Esempio: double power(double b, int k){ return (k<=0) ? 1 : powerIt(b,k,b,1); } Invariante di programma:è sempre vero che qui k>0
INVARIANTI DI CICLO Un invariante di cicloè una relazione sem- pre vera, in un dato punto del programma, a ogni iterazione. • Identificare un invariante di ciclo è una forma di progetto. • Invarianti diversi suggeriscono di norma algoritmi diversi, che quasi sempre hanno diversa efficienza.
PROBLEMA: CALCOLO DI bk Un approccio iterativo Posto bk = vk, si può scrivere: b0= v0= 1 per i=0 bi = vi= b * bi-1 = b * vi-1per i>0 in particolare: bk = vk per i=k Un possibile invariante:bk = vi * bk-i
PROBLEMA: CALCOLO DI bk Perché bk = bk-i *vi è un invariante? Al generico passo 0<i<k, bk = vi* bk-i Moltiplicando e dividendo per b: bk = (vi*b) *bk-i-1 =vi+1* bk-(i+1) che è l’invariante al passo (i+1)-esimo. In particolare: • per i=0, bk = v0* bk = bkpurché v0 =1 • per i=k, bk = vk* b0 = vk condizione iniziale
PROBLEMA: CALCOLO DI bk Come usarlo per progettare l’algoritmo? • inizialmente, v = v0 =1 • a ogni passo si deve trasformare l’invariantebk = vi* bk-inella formabk =vi+1* bk-(i+1) che deve assumere al passo successivo • ciò si ottiene ponendo, a ogni passo v’ = b * v i’ = i + 1
CALCOLO DI bk : L’INVARIANTE double powerIt(double b, int k, double v, int i){ return (i==k) ? v :powerIt(b,k,v*b,i+1); } double power(double b, int k){ return (k==0) ? 1 : powerIt(b,k,1,0); } i’ = i+1 V’ = Vi+1= Vi* b V0=1 i=0
PROGETTARE bk PER INVARIANTI Partendo da relazioni diverse si ottengono approcci diversi, con diversa efficienza. Un diverso invariante: • k=0 b0 = 1 • k>0 k pari bk = (b2) k/2 k dispari bk = b * bk-1 b*b: non richiede disaper fare potenze richiede di saper fare un prodotto
PROGETTARE bk PER INVARIANTI Come usarlo per progettare l’algoritmo? • a ogni passo si deve riscriverebkin una delle due forme date • ciò si ottiene ponendo, a ogni passo • se k è pari: b’ = b * b k’ = k/2 • se k è dispari: b’ = b k’ = k-1e moltiplicando per b richiede una operazione dopo la fasedi modifica di b e k soluzione ricorsiva
PROGETTARE bk PER INVARIANTI boolean odd(int n){return n%2==1; } double pow(double b, int k){ return (k==0) ? 1 : odd(k) ? pow(b, k-1)* b: pow(b*b, k/2); } ricorsionenon-tail (Complessità dell’ordine di log2 k)
PROGETTARE bk PER INVARIANTI UN APPROCCIO ITERATIVO Un ulteriore invariante:bk = t * vn • k=0 n=0, t=1 bk = t * v0 = 1 • k>0 • se n è pari: bk = t * (v2) n/2 • se n è dispari: bk = t * v * vn-1
PROGETTARE bk PER INVARIANTI Progetto dell’algoritmo: • a ogni passo si deve trasformare l’invariantebk = t * vnin una delle due forme date • ciò si ottiene ponendo: • se n è pari n’ = n/2,t’ = t,v’ = v2 • se n è dispari n’ = n-1,t’ = t*v,v’ = v Interessante: b e k in realtà non si usano!
PROGETTARE bk PER INVARIANTI boolean odd(int n){return n%2==1;} double powIt(double b, int k, double t, double v, int n){ return (n==0) ? t : odd(n) ? powIt(b,k,t*v,v,n-1) : powIt(b,k,t,v*v,n/2); } Come previsto, b e knon servono!Quindi li possiamo togliere…!!
PROGETTARE bk PER INVARIANTI boolean odd(int n){return n%2==1;} double powIt(double t, double v, int n){ return (n==0) ? t : odd(n) ? powIt(t*v, v, n-1) : powIt(t, v*v, n/2); } double power(double b, int k){ return (k==0) ? 1 : powIt(1,b,k); }
ESERCIZIO: MOLTIPLICAZIONE Obiettivo: calcolare p = x* y Sfruttiamo l’invariante: y = Q * B + R dove • B è un intero positivo • Q (quoziente) = y/B • R (resto) = y%B
ESERCIZIO: MOLTIPLICAZIONE Obiettivo: calcolare p = x* y Sostituendo: p = x * y= x*(Q * B + R)= = x * (B * (y/B))+x*(y%B) Caso particolare: y=0 p = x * y= x*0= 0
ESERCIZIO: MOLTIPLICAZIONE Approccio ricorsivo: si applica direttamente la relazione trovatap = x*B * (y/B) ) + x*(y%B) Ad esempio, scegliendo B=2: int MulNatR(int x, int y){ return (y==0) ? 0 : MulNatR(x*2, y/2) + x*(y%2); } Occorre fare un’operazione dopo la chiamata ricorsiva ricorsione non-tail
ESERCIZIO: MOLTIPLICAZIONE Approccio ricorsivo: si applica direttamente la relazione trovatap = x*B * (y/B) ) + x*(y%B) Ad esempio, scegliendo B=2: int MulNatR(int x, int y){ return (y==0) ? 0 : MulNatR(x*2, y/2) + x*(y%2); } Operazione primitiva che suppo-niamo di saper già fare (è una moltiplicazione per 0 o per 1) Operazioni primitive che supponiamo di sa-per già fare (moltiplicazione/divisione per 2)
ESERCIZIO: MOLTIPLICAZIONE Verso un approccio iterativo Cerchiamo un invariante di ciclo p = x * y + z Ponendo y=Q*B+Re trasformando: p = (x*B) * Q + (x*R + z) = x’ * y’ + z’ dove si è posto y’ = Q = y/B,x’ = x*B,z’ = z + x*R Caso particolare: y=0 p = z
ESERCIZIO: MOLTIPLICAZIONE Invariante di ciclo: p = x * y + z Trasformazione: p = x’ * y’ + z’ y’ = Q = y/B,x’ = x*B,z’ = z + x*R int MulNatIt(int x, int y, int z){ return(y==0) ? z : MulNatIt(x*2, y/2, z+x*(y%2)); } Operazioni primitive: supponiamo di saper giàmoltiplicare, dividere e modulare per 2.
ESERCIZIO: MOLTIPLICAZIONE Perché supponiamo di saper già moltiplicare, e dividere per 2 (trovando anche il resto) ? Perché l’elaboratore è intrinsecamente capace di farlonella propria ALU: • moltiplicazione per 2 = shift a sinistra (<<) • divisione per 2 = shift a destra (>>) • moltiplicazione per (y%2) = 0, se y è pari (y%2 vale 0) y, se y dispari (y%2 vale 1)
ESERCIZIO: MOLTIPLICAZIONE Il codice finale che ne risulta: int MulNatIt(int x, int y, int z){ return (y==0) ? z : odd(y): MulNatIt(x<<1, y>>1, z+x) : MulNatIt(x<<1, y>>1, z); } boolean odd(int n){return n%2==1;} y%2 = 1 x/2 y/2 y%2 = 0
UNA RIFLESSIONE DI FONDO • L’impostazione funzionale è sempre costruttiva. Ma si può sempre solo creare? • Perché creare una versione nuova di un accumulatore ad ogni passo, quando l’elaboratore di Von Neumann permette la modificadel contenuto di una cella di memoria?
UNA PROPOSTA • È possibile riusare una stessa area datisenza bisogno di crearne una nuova ad ogni passo computazionale? • Ci sono controindicazioni?
VARIABILI NEI LINGUAGGI IMPERATIVI Una variabilein un linguaggio imperativo • non è solo un sinonimo per un datocome in matematica • è un’astrazione della cella di memoria • associata a due diverse informazioni: • il contenuto (R-value) • l’indirizzo a cui si trova (L-value) 3.22 a x
ESPRESSIONI CON EFFETTI COLLATERALI • Le espressioni che contengono variabili, oltre a denotare un valore,possono a volte comportare effetti collaterali sulle variabili coinvolte. • Un effetto collaterale è una modifica del valore della variabile (R-value) causato da particolari operatori: • operatore di assegnamento • operatori di incremento e decremento
ASSEGNAMENTO • L’assegnamento è un particolare tipo di espressione • come tale denota comunque un valore!! con un effetto collaterale:quello di cambiare il valore della variabile. • Sintassi variabile = espressione • Esempi di espressioni di assegnamento: j = 0 k = j + 1
ASSEGNAMENTO L’espressione di assegnamento variabile = espressione • denota il valore dell’ espressione • ma cambia anche il valore della variabile: il nuovo valore della variabile è quello denotato dalla espressione.
ESEMPIO Se k valeva 2, l’espressione k = 7 • denota il valore 7 • e cambia il valore di k,che d’ora in poi vale 7 (non più 2)
ESEMPIO Se k valeva 2, l’espressione j = k+1 • denota il valore 3 • e cambia il valore di j,che d’ora in poi vale 3 (qualunque valore avesse prima) L’assegnamento è distruttivo
ESPRESSIONI DI ASSEGNAMENTO Il valore denotato dall’espressione di assegnamento può essere usato in altre espressioni.Ad esempio, 3 + (k=7) • denota il valore 10 • e cambia in 7 il valore di k
ASSEGNAMENTO & VARIABILI Una variabilein una espressione di assegnamento: • è intepretata come il suo R-value, se compare a destra del simbolo = • è intepretata come il suo L-value, se compare a sinistra del simbolo = 3.22 a x
ESEMPIO Se x valeva 2, l’espressione x = x + 1 • denota il valore 3 • e cambia in 3 il valore di x • il simbolo x a destra dell’operatore = denota il valore attuale (R-value) di x, cioè 2 • il simbolo x a sinistra dell’operatore = denota la cella di memoria associata a x (L-value), a cui viene assegnato il valore dell’espressione di destra (3) • l’espressione nel suo complesso denota il valore della variabile dopo la modifica, cioè 3.
ASSEGNAMENTO: ASSOCIATIVITÀ • Come tutti gli operatori, anche l’operatore di assegnamento deve avere una sua associatività k = j = 1 Prima k=j, o prima j=1 ? • l’operatore di assegnamento è associativo a destra: ciò consente espressioni di assegnamento multiplo
ASSEGNAMENTO: ASSOCIATIVITÀ Esempi k = j = 1interpretato come k = (j = 1) i = j = k = 0interpretato come i = (j = (k=0)) i = k + 5 = 6NO: k+5 non ha un L-value! Nota: anche volendo, sarebbe stato impossibile farlo associativo a sinistra, in quanto ciò avrebbe reso molte espressioni prive di significato. Ad esempio: k = j = 2 interpretato come (k=j) = 2 ??? Equivarrebbe a scrivere 1 = 2 !!!!
INCREMENTO (++) E DECREMENTO (--) Gli operatori di incremento e decremento sono usabili in due modi • come pre-operatori: ++v • come post-operatori: v++ primaincremento, poiuso primauso, poiincremento
ESEMPI int i, j, k = 5; i = ++k /* i vale 6, k vale 6 */ i = k++ /* i vale 5, k vale 6 */ int i=4, j, k = 5; j = i + k++; /* j vale 9, k vale 6 */ j = ++k - k++; /* in cerca di guai! */
ATTENZIONE…!! int k = 6; j = ++k - k++; /* in cerca di guai! */ Detti x = ++k e y = k++, • è certo che l’espressione venga valutata come j = x - y (da sinistra a destra) • è certo che alla fine k valga 8 • ma non si sa se venga calcolato prima x o prima y,e qui la cosa fa molta differenza! • se prima x, poi y j = 7 - 7 = 0 • se prima y, poi x j = 8 - 6 = 2
UN ESEMPIO Ad esempio, se c vale 20, l’espressione vale 68... main() { int f, c = 20; f = 32 + c * 9 / 5; } L’espressionef = 32 + c * 9 / 5 • recupera l’ R-value della variabile c • calcola il corrispondente valore Fahrenheit e lo assegna alla variabile f (interpretata come L-value effetto collaterale) • scarta il valore denotato dall’espressione di assegnamento (che non viene più utilizzato) … quindi a f viene asse-gnato il valore 68. L’espressione f=68 denotaancora 68, che però vienescartato.
ISTRUZIONI • Le istruzioni esprimono azioni che, una volta eseguite, comportano una modifica permanente dello stato interno del pro-gramma o del mondo circostante. • Le strutture di controllo permettono di aggregare istruzioni semplici in istruzioni più complesse.
ISTRUZIONI • Una istruzione C è espressa dalle seguenti produzioni: <istruzione> ::= <istruzione-semplice> <istruzione> ::= <istruzione-di-controllo> <istruzione-semplice> ::= <espressione>; • Quindi, qualsiasi espressione seguita da un punto e virgola è una istruzione semplice.
ESEMPI DI ISTRUZIONI SEMPLICI x = 0; y = 1; /* due istruzioni */ x = 0, y = 1; /* una istruzione */ k++; 3; /* non fa nulla */ ; /* istruz. vuota*/
ISTRUZIONI DI CONTROLLO Una istruzione di controllo può essere: • una istruzione composta (blocco) • una istruzione condizionale (selezione) • una istruzione di iterazione(ciclo) come specificato dalla produzione: < istruzione-di-controllo > ::=<blocco> | <selezione> | <iterazione>
ISTRUZIONI DI CONTROLLO Le istruzione di controllo sono alla base della programmazione strutturata(Dijkstra, 1969). Concetti chiave: • concatenazioneocomposizione • selezione o istruzione condizionaleramifica il flusso di controllo in base al valore vero o falso di una espressione (“condizione di scelta”) • ripetizione o iterazioneesegue ripetutamente un’istruzione finché rimane vera una espressione (“condizionedi iterazione”)