1 / 45

DEFINIZIONE DI NUOVE FUNZIONI & STRATEGIE DI COMPOSIZIONE

DEFINIZIONE DI NUOVE FUNZIONI & STRATEGIE DI COMPOSIZIONE. La capacità di definire nuove funzioni permette: di definire nuove operazioni di introdurre variabili per denotare i dati in modo simbolico di esprimere la ripetizione di una espressione per un numero (prefissato o meno) di volte.

saki
Download Presentation

DEFINIZIONE DI NUOVE FUNZIONI & STRATEGIE DI COMPOSIZIONE

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. DEFINIZIONE DI NUOVE FUNZIONI & STRATEGIE DI COMPOSIZIONE La capacità di definire nuove funzioni permette: • di definire nuove operazioni • di introdurre variabili per denotare i dati in modo simbolico • di esprimere la ripetizione di una espressione per un numero (prefissatoo meno) di volte.

  2. STRATEGIE DI COMPOSIZIONE Tre grandi approcci: 1) la composizione di funzioni; 2) le espressioni condizionali; 3) la ricorsione. Le funzioni definibili in termini di un insieme prescelto di primitive e delle precedenti strategie di composizione costituiscono un insieme detto delle funzioni ricorsive generali.

  3. 1) COMPOSIZIONE DI FUNZIONI I parametri in una chiamata di funzione pos- sono consistere/comprendere altre funzioni Es: f(x) + g(f(x), q(x + f(y))) • x1 = f(x) • x2 = f(x) (mossa evitabile da un automa “intelligente”) • x3 = f(y) • x4 = x + x3 • x5 = q( x4 ) • x6 = g( x2,x5 ) • x7 = x1 + x6

  4. 2) ESPRESSIONE CONDIZIONALE • L’espressione condizionale riflette la consuetudine matematica di definire le funzioni per elencazione di casi. • Esempio: abs: N -> N abs(x) vale x se x  0 abs(x) vale -x se x  0

  5. 3) LA RICORSIONE • La ricorsione consiste nella pos-sibilità di definire una funzione in termini di se stessa. • È basata sul principio di induzionematematica: • se una proprietà P vale per n=n0 • e si può provare che, assumendola valida per n, allora vale per n+1 allora P vale per ogni nn0

  6. LA RICORSIONE • Operativamente, risolvere un problema con un approccio ricorsivo comporta • di identificare un “caso base” la cui soluzione sia “ovvia” • di riuscire a esprimere la soluzione al caso generico n in termini dello stesso problema in uno o più casi più semplici (n-1, n-2, etc).

  7. LA RICORSIONE: ESEMPIO Esempio !: N  N n! vale 1 se n  0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { return n==0 ? 1 : n*fact(n-1); }

  8. LA RICORSIONE: ESEMPIO Attenzione: la codifica non corrisponde alla specifica!! Il 2° caso si applica per n0,cioè anche per n<0 !!MA COSÌ PUÒ NON TERMINARE! Esempio !: N  N n! vale 1 se n  0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { return n==0 ? 1 : n*fact(n-1); }

  9. LA RICORSIONE: ESEMPIO Esempio !: N  N n! vale 1 se n  0 n! vale n*(n-1)! se n > 0 Codifica: int fact(int n) { /* return n==0 ? 1 : n*fact(n-1); */ return n>0 ? n*fact(n-1) : 1; } Nuova codifica

  10. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n* fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); }

  11. ESEMPIO: FATTORIALE Si valuta l’espressione che costituisce il parametro attuale (nell’environment del main) e si trasmette alla funzione fatt una copia del valore così ottenuto (7). • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); }

  12. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } La funzione riceve una copia del valore 7 e la lega al simbolo n. Poi valuta l’espres- sione condizionale: ciò impone di valutare unaespressione che contiene una nuova chiamata di funzione.

  13. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Si valuta quindi, nell’environment di fatt, l’espressione n-1 (che vale 6), e si effettua una nuova chiamata al servitore fatt, pas- sandogli una copia del valore 6.

  14. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il (nuovo) servitore riceve quindi una copia del valore 6 e, come sopra, valuta l’espres- sione condizionale. Ciò lo porta a dover fare una nuova chiamata passando 5.

  15. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } … la cosa prosegue, servitore dopo servitore.. ...

  16. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Prima o poi, dato che il valore passato cala ogni volta, si giunge a invocare fatt con parametro 1. In questo caso, la valutazione dell’espressione dà come risultato 1.

  17. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Ciò chiude la sequenza di chiamate ricorsive. Il controllo torna al servitore precedente, che può finalmente valutare l’espressione n*1 (valutando n nel suo environment, dove vale 2) ottenendo 2.

  18. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il valore 2 viene restituito al servitore pre- cedente, che a sua volta può così valutare l’espressione n*2 (valutando n nel suo environment, dove vale 3) ottenendo 6.

  19. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } … la cosa prosegue ...

  20. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Prima o poi, a forza di retrocedere, si torna al primo servitore attivato, che può quindi valutare l’espressione n*720 (valutando n nel suo environment, dove vale 7), giun- gendo così a trovare il valore 5040.

  21. ESEMPIO: FATTORIALE • Servitore & Cliente: int fatt(int n) { return (n>0) ? n*fatt(n-1) : 1; } main(){ int z = 5; int fz = fatt(z+2); int f6 = fatt(6); } Il valore 5040, restituito dal servitore fatt, può quindi essere usato per inizializzare la variabile fz.

  22. UN ALTRO ESEMPIO Problema: calcolare la somma dei primi N interi Specifica: Considera la somma (1+2+3+...+(N-1)+N come composta di due termini: • (1+2+3+...+(N-1)) • N Esiste un caso banale assolutamente ovvio: • la somma fino a 1 vale 1.

  23. UN ALTRO ESEMPIO Il primo termine non è altro che la soluzione allo stesso problema inun caso più semplice Problema: calcolare la somma dei primi N interi Specifica: Considera la somma (1+2+3+...+(N-1)+N come composta di due termini: • (1+2+3+...+(N-1)) • N Esiste un caso banale assolutamente ovvio: • la somma fino a 1 vale 1. Il secondo termine è un valore già noto.

  24. UN ALTRO ESEMPIO Problema: calcolare la somma dei primi N interi Codifica: int sommaFinoA(int n){ return (n==1) ? 1 : sommaFinoA(n-1) + n; }

  25. 0, se n=0 fib (n) = 1, se n=1 fib(n-1) + fib(n-2), altrimenti UN TERZO ESEMPIO Problema: calcolare l’N-esimo numero di Fibonacci

  26. UN TERZO ESEMPIO Problema: calcolare l’N-esimo numero di Fibonacci Codifica: unsigned fibonacci(unsigned n) { return (n<2) : n ? fibonacci(n-1) + fibonacci(n-2); }

  27. UN TERZO ESEMPIO Ricorsione non lineare: ogniinvocazione del servitore causadue nuove chiamate al servitore medesimo. Problema: calcolare l’N-esimo numero di Fibonacci Codifica: unsigned fibonacci(unsigned n) { return (n<2) : n ? fibonacci(n-1) + fibonacci(n-2); }

  28. UNA RIFLESSIONE • Negli esempi di ricorsione visti finora • fattoriale • somma dei primi N interi • calcolo dell’N-esimo numero di Fibonacci • si inizia a sintetizzare il risultato solo dopo che si sono aperte tutte le chiamate, “a ritroso”, mentre le chiamate si chiudono.

  29. UNA RIFLESSIONE • Le chiamate ricorsive decompongono via via il problema,ma non calcolano nulla • Il risultato viene sintetizzato a partire dalla fine, perché prima occorre arrivare al caso “banale”: • il caso “banale” fornisce il valore di partenza • poi, e solo poi, si sintetizzano, “a ritroso”, i successivi risultati parziali.

  30. UNA RIFLESSIONE Ciò indica che tali soluzioni • sono sintatticamente ricorsive • e danno luogo a un processo computa-zionale effettivamente ricorsivo.

  31. m, se m=n MCD(m, n) = MCD(m-n, n), se m>n MCD(m, n-m), se m<n UN ESEMPIO DIVERSO Problema: trovare il Massimo Comun Divisore tra N e M

  32. UN ESEMPIO DIVERSO Problema: calcolare il Massimo Comun Divisore tra N e M Codifica: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m); }

  33. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); }

  34. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Si valutano i parametri attuali e si invoca la funzione con parametri 36 e 15

  35. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Si legano i parametri 36 e 15 ai parametri formali m e n, e si valuta l’espressione condizionale.

  36. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Poiché 36  15, si invoca nuovamente la funzione con parametri m-n (21) e n (15).

  37. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Il nuovo servitore, poiché 21  15, fa la stessa cosa e invoca nuovamente la funzione con parametri m-n (6) e n (15).

  38. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Il nuovo servitore, poiché 6  15, invoca un ulteriore servitore con parametri m (6) e n-m (9).

  39. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Poiché 6  9, si ha una nuova chiamata con parametri m (6) e n-m (3).

  40. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Poiché 6  3, si ha una nuova invocazione (l’ultima!) con parametri m-n (3) e n (3).

  41. UN ESEMPIO DIVERSO • Servitore & Cliente: int mcd(int m, int n){ return (m==n) : m ? (m>n) ? mcd(m-n, n) : mcd(m, n-m);} main(){ int m = mcd(36,15); } Poiché 3 = 3, il servitore termina e restituisce come risultato 3.

  42. UN ESEMPIO DIVERSO • Perché questo esempio è “diverso” ? • il risultato viene sintetizzato via via che le chiamate si aprono, “in avanti” • quando le chiamate si chiudono non si fa altro che riportare indietro, fino al cliente, il risultato ottenuto.

  43. UNA RIFLESSIONE • La soluzione ricorsiva individuata per l’MCD è sintatticamente ricorsiva... • … ma dà luogo a un processo computa-zionale diverso dal precedente: • un processo computazionale ITERATIVO • Il risultato viene sintetizzato in avanti • ogni passo decompone e calcola • e porta in avanti il nuovo risultato parziale

  44. UNA RIFLESSIONE Ogni processo computazionale ITERATIVO calcola a ogni passo un risultato parziale • dopo k passi, si ha a disposizione il risultato parziale relativo al caso k • questo non è vero nei processi computa-zionali ricorsivi • là, finché non si sono aperte tutte le chiamate, non è disponibile nessun risultato!

  45. RICORSIONE TAIL Una ricorsione che realizza un processo computazionale ITERATIVO è una ricorsione solo apparente • la chiamata ricorsiva è sempre l’ultima istruzione • i calcoli sono fatti prima • la chiamata serve solo, dopo averli fatti, per proseguire la computazione • questa forma di ricorsione si chiama RICORSIONE TAIL(“ricorsione in coda”)

More Related