La principale modalità di calcolo è l`applicazione di funzioni Nei

Transcript

La principale modalità di calcolo è l`applicazione di funzioni Nei
1
La principale modalità di calcolo è l’applicazione di funzioni
Nei linguaggi funzionali “puri” non esistono strutture di controllo predefinite per la realizzazione di cicli quali for, while, repeat
Un programma è un insieme di dichiarazioni
Tecnica algoritmica fondamentale: riduzione a sottoproblemi
2
Esempio: somma di ore
Un’ora del giorno si può rappresentare mediante una coppia di interi
(ore,minuti):
int * int
dove ore è compreso tra 0 e 23, e minuti è compreso tra 0 e 59.
Problema: calcolare il risultato della somma di due ore
Input: due coppie di interi, h1 e h2, rappresentanti ore
Output: una coppia di interi, rappresentante la somma di h1 e h2
somma di (3,15) e (4,20) = (7,35)
somma di (3,45) e (4,20) = (8,5)
somma di (23,45) e (0,20) = (0,5)
3
Procedimento:
somma_ore (h1,m1) (h2,m2):
eseguire la somma dei minuti m1 e m2,
ottenendo (h3,m3);
sommare h1+h2+h3;
sia h4 il risultato di tale somma;
se h e’ il resto della divisione di h4 per 24,
il risultato e’ (h,m3)
4
Sottoproblema: eseguire la somma di minuti
somma_minuti di 45 e 20 = (1,5)
Come otteniamo i minuti?
45 + 20 = 65
5 e‘ il resto della divisione 65/60
cioe‘ 5 = (45+20) mod 60
Come otteniamo le ore?
1 è il risultato della divisione intera (45+20)/60
Procedimento:
somma_minuti (m1,m2):
sia m=m1+m2;
il risultato e’ (m/60, m mod 60)
In OCaml:
(* somma_minuti : int * int -> int * int *)
let somma_minuti (m1,m2) =
let m = m1+m2
(* sia m=m1+m2 *)
in (m/60, m mod 60)
(* nell’espressione (m/60, m mod 60) *)
5
Il programma principale
Procedimento:
somma_ore (h1,m1) (h2,m2):
sia (h3,m3) = somma_minuti (m1,m2);
il risultato e’ ((h1+h2+h3) mod 24,m3)
La funzione principale:
(* somma_ore : (int * int) * (int * int) -> int * int *)
let somma_ore ((h1,m1),(h2,m2)) =
let (h3,m3) = somma_minuti (m1,m2)
in ((h1+h2+h3) mod 24, m3)
6
Esercizio: scrivere un programma che, data la rappresentazione di una data nella
forma (giorno,mese), calcoli la data che rappresenta il giorno successivo. Ad esempio
si avrà:
giorno_succ (24,marzo) = (25,marzo)
giorno_succ (28,febbraio) = (1,marzo)
giorno_succ (31,dicembre) = (1,gennaio)
7
Nei linguaggi funzionali “puri” non esistono strutture di controllo predefinite per la realizzazione di cicli quali for, while, repeat, ma il
principale meccanismo di controllo è la ricorsione.
RICORSIONE
La ricorsione è una tecnica per risolvere problemi complessi riducendoli a problemi più
semplici dello stesso tipo.
Per risolvere un problema ricorsivamente occorre:
1. Identificare i casi semplicissimi (casi di base) che possono essere risolti immediatamente
2. Dato un generico problema complesso, identificare i problemi più semplici dello
stesso tipo la cui soluzione può aiutare a risolvere il problema complesso
3. Assumendo di saper risolvere i problemi più semplici (ipotesi di lavoro), determinare come operare sulla soluzione dei problemi più semplici per ottenere la
soluzione del problema complesso
8
Esempio: la funzione fattoriale
IN → IN
n! = 1 × 2 × ... × (n − 1) × n
Per convenzione: 0! = 1
f act(n): metodo ricorsivo per calcolare n!
1. Caso base: f act(0) = 1
2. Volendo calcolare f act(n), per n > 0, calcolare f act(n − 1)
3. Per ottenere f act(n), moltiplicare f act(n − 1) per n
Definizione ricorsiva del fattoriale:



f act(n) = 

1
se n = 0
n × f act(n − 1) altrimenti
Il fattoriale è “definito in termini di se stesso”, ma per un caso “più facile”.
9
In OCaml
let rec fact n =
if n=0 then 1
else n * fact(n-1)
La parola chiave rec è necessaria per indicare che la definizione è ricorsiva
# let fact n = if n=0 then 1 else n * fact(n-1);;
Characters 36-40:
let fact n = if n=0 then 1 else n * fact(n-1);;
^^^^
Unbound value fact
10
Calcolo mediante riduzioni
Processo di calcolo del valore di fact 3:
fact 3 =
=
=
=
=
=
=
=
=
=
=
=
if 3=0 then 1 else 3 * fact(3-1)
3 * fact(3-1)
3 * fact 2
3 * if 2=0 then 1 else 2 * fact(2-1)
3 * (2 * fact 1)
3 * (2 * if 1=0 then 1 else 1 * fact(1-1))
3 * (2 * (1 * fact 0))
3 * (2 * (1 * if 0=0 then 1
else 0 * fact(0-1)))
3 * (2 * (1 * 1))
3 * (2 * 1)
3 * 2
6
Che succede se fact à applicata a un numero negativo?
11
Definizioni ricorsive di funzioni
per calcolare F (n):
se n e’ un caso base, riporta la soluzione per il caso n
altrimenti: risolvi i problemi piu’ semplici
F (n1), F (n2), ....F (nk )
(* chiamate ricorsive *)
combina le soluzioni ottenute e riporta
combina(F (n1), F (n2), ....F (nk ))
Un processo ricorsivo termina se le chiamate ricorsive si avvicinano ai casi di base:
dopo un numero finito di chiamate ricorsive si arriva a casi base.
12
Esempio: calcolo del massimo di una serie di numeri letti da tastiera
Esempio: dato un numero intero non negativo n, calcolare il massimo di una sequenza
di n numeri interi letti da tastiera.
Operazioni di lettura e scrittura: programmazione imperativa
Input da tastiera
read_line : unit -> string
read_int : unit -> int
read_float : unit -> float
# let x=read_int ();;
45
val x : int = 45
# let x=read_line();;
pippo e pluto
val x : string = "pippo e pluto"
13
# let x=read_int();;
3pippo
Exception: Failure "int_of_string".
14
Un algoritmo ricorsivo per risolvere il problema
Risolvere il problema di determinare il massimo di n−1 numeri è più semplice di quello
di determinare il massimo di n numeri: possiamo risolvere il problema ricorsivamente.
Procedimento:
max_n (n):
se n<=0 riportare 0;
altrimenti leggere il primo intero x1;
se n=1 riportare x1 stesso,
altrimenti riportare max x1 (max_n (n-1))
Il programma
(* max_n : int -> int *)
let rec max_n n=
if n<=0 then 0
else let x = read_int()
in if n=1 then x
else max x (max_n (n-1))
15
Esempio 2
Leggere da tastiera una sequenza di interi positivi e calcolarne il massimo. La sequenza
termina con un numero non positivo.
Lettura di un valore che deve essere utilizzato più di una volta:
Leggere un intero x e riportare la coppia (100/x, 200/x)
(100/read_int(), 200/read_int()) ⇐ NO!
# let x = read_int() in (100/x, 200/x);;
25
- : int * int = (4, 8)
# let x = read_int()
in let y = read_int()
in (x/y, x mod y);;
10
3
- : int * int = (3, 1)
16
Algoritmo ricorsivo
max_of :
leggi un numero x;
if x <= 0 then return x
else return il massimo tra x
e il massimo tra i numeri
letti successivamente
(* nuova chiamata a max_of *)
(* max_of: unit -> int *)
let rec max_of () =
let x = read_int()
in if x <= 0 then x
else max x (max_of ())
17
Esempio 3
Problema: Definire una funzione che, dato un intero n, legga da tastiera una sequenza di interi, separati tra loro da Enter, e terminata dall’immissione della stringa
vuota, e riporti true se tutti i numeri letti sono minori di n, false altrimenti.
Utilizziamo read_line per la lettura di ciascuna linea; dopo aver verificato che la
stringa letta è diversa dalla stringa vuota "", la trasformarmiamo in un intero con la
funzione di conversione
int_of_string:
string -> int
Prima versione
let rec tutti_minori_di n =
let stringa = read_line()
in if stringa="" then true
else let k = int_of_string stringa
in k<n && tutti_minori_di n
In questo caso, la chiamata ricorsiva non “diminuisce” alcuna misura.
È ovvio che se non si immette mai la stringa vuota il programma non termina!
18
Esempi di esecuzione
# tutti_minori_di 100;;
5
6
8
10
- : bool = true
# tutti_minori_di 100;;
7
200
- : bool = false
Il programma non ha aspettato l’immissione della stringa vuota!!
La valutazione delle espressioni booleane è “pigra”:
quando si valuta k<n && tutti_minori_di n:
viene valutato k<n: se è falso, questo è sufficiente a determinare che tutta la “congiunzione” è falsa, quindi non viene effettuata la chiamata ricorsiva.
19
Seconda versione
Usando dichiarazioni locali si può forzare l’ordine di valutazione delle espressioni:
let rec tutti_minori_di n =
let stringa = read_line()
in if stringa="" then true
else let k = int_of_string stringa
in let result= tutti_minori_di n
in k<n && result