Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


PROGRAMMAZIONE FUNZIONALE (PARTE 2/3), Sbobinature di Programmazione Avanzata

SBOBINE delle lezioni di programmazione funzionale riguardo l'uso delle liste nel linguaggio di programmazione Ocaml

Tipologia: Sbobinature

2021/2022

In vendita dal 17/02/2024

DomenicoTucci
DomenicoTucci 🇮🇹

11 documenti

1 / 32

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Liste
Sequenze finite di elementi di uno stesso tipo:
# [1;2;3;4];;
- : int list = [1; 2; 3; 4]
# [true; false; 3>0 && 7<=0];;
- : bool list = [true; false; false]
# [(1,"pippo");(2,"pluto");(3,"topolino")];;
- : (int * string) list =
[(1, "pippo"); (2, "pluto"); (3, "topolino")]
list è un costruttore di tipi: se T è un tipo, T list è il tipo delle liste con elementi di tipo T.
int list
bool list
(int * string) list
’a list
La lista vuota è denotata da [ ]. È un valore polimorfo, di tipo ’a list.
L’operazione di inserimento in testa (cons) è denotata da ::, infisso:
[1;2] = 1::[2] = 1::(2::[])
DEFINIZIONE INDUTTTIVA DELLE LISTE
1) La lista vuota, [ ], è una α list;
2) se x è di tipo α e xs è una α list, allora
(x::xs) è una α list;
3) nient’altro è una α list.
La definizione induttiva fornisce un metodo
per generare tutte le liste di un dato tipo, per
stadi
(xs significa per convenzione: lista di x tipo?)
COSTRUTTORI E SELETTORI DELLE LISTE
NB: i costruttori sono importanti perché devo sapere cosa mettere in un pattern
NBB: non abbiamo i selettori predefiniti per triple e quadruple, perché c’è il pattern matching
I COSTRUTTORI del tipo ‘a list sono:
- [ ] : ’a list Lista vuota, costruttore costante
- ( :: ) : ’a -> ’a list -> ’a list Inserimento in testa (cons)
Si applica da un alfa a un alfa list, e costruisce un alfa list
ES: [1;2;3] = 1 :: [2;3] = 1 :: (2 :: [3]) = 1 :: (2 :: (3 :: []))
I SELETTORI del tipo ‘a list sono:
- List.hd : ’a list -> ’a Head (primo elemento)
- List.tl : ’a list -> ’a list Tail (resto della lista)
TAIL della lista non è l’ultimo elemento, ma il resto della lista escluso il primo
elemento (un’altra lista che ha come head il secondo elemento della lista di prima)
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20

Anteprima parziale del testo

Scarica PROGRAMMAZIONE FUNZIONALE (PARTE 2/3) e più Sbobinature in PDF di Programmazione Avanzata solo su Docsity!

Liste

Sequenze finite di elementi di uno stesso tipo: # [1;2;3;4];;

_- : int list = [1; 2; 3; 4]

[true; false; 3>0 && 7<=0];;

  • : bool list = [true; false; false]

[(1,"pippo");(2,"pluto");(3,"topolino")];;

  • : (int * string) list = [(1, "pippo"); (2, "pluto"); (3, "topolino")]_ list è un costruttore di tipi: se T è un tipo, T list è il tipo delle liste con elementi di tipo T. int list bool list (int * string) list ’a list La lista vuota è denotata da [ ]. È un valore polimorfo , di tipo ’a list. L’operazione di inserimento in testa ( cons ) è denotata da :: , infisso: [1;2] = 1::[2] = 1::(2::[]) DEFINIZIONE INDUTTTIVA DELLE LISTE
  1. La lista vuota, [ ], è una α list;
  2. se x è di tipo α e xs è una α list, allora (x::xs) è una α list;
  3. nient’altro è una α list. La definizione induttiva fornisce un metodo per generare tutte le liste di un dato tipo, per stadi (xs significa per convenzione: lista di x tipo?) COSTRUTTORI E SELETTORI DELLE LISTE NB: i costruttori sono importanti perché devo sapere cosa mettere in un pattern NBB: non abbiamo i selettori predefiniti per triple e quadruple, perché c’è il pattern matching I COSTRUTTORI del tipo ‘a list sono:
  • [ ] : ’a list Lista vuota, costruttore costante
  • ( :: ) : ’a - > ’a list - > ’a list Inserimento in testa (cons) Si applica da un alfa a un alfa list, e costruisce un alfa list ES: [1;2;3] = 1 :: [2;3] = 1 :: (2 :: [3]) = 1 :: (2 :: (3 :: [])) I SELETTORI del tipo ‘a list sono:
  • List.hd : ’a list - > ’a Head (primo elemento)
  • List.tl : ’a list - > ’a list Tail (resto della lista) TAIL della lista non è l’ultimo elemento, ma il resto della lista escluso il primo elemento (un’altra lista che ha come head il secondo elemento della lista di prima)

NB: Costruttori e selettori sono importanti perché sono operazioni che caratterizzano quella struttura dati dal punto di vista astratto. Potremmo definire la struttura dati anche partendo da equazioni/relazioni tra i costruttori e i selettori (i selettori non sono definiti sulla lista vuota!) ES: # List.hd [1;2;3;4];;

_- : int = 1

List.tl [1;2;3;4];;

  • : int list = [2; 3; 4]

List.hd(List.tl [1;2;3;4]);;

  • : int = 2

List.hd [];;

Exception: Failure "hd".

List.tl [];;

Exception: Failure "tl"._ DEFINIZIONE RICORSIVA DI OPERAZIONI SULLE LISTA Le liste si definiscono induttivamente, come i numeri naturali: Sulle liste si possono definire operazioni ricorsivamente: - si definisce il valore dell’operazione per il caso base [ ] - assumendo di saper calcolare il valore dell’operazione per una lista rest (ipotesi di lavoro), si determina come calcolarne il valore per la generica lista x::rest (di cui rest è la coda). ESEMPIO: lunghezza di una lista Problema: data una lista lst di tipo ’a list, determinare il numero di elementi di lst (la lunghezza della lista). length: ’a list - > int Caso base: la lista è vuota. La sua lunghezza allora è 0. Caso ricorsivo: assumiamo (ipotesi di lavoro) di saper calcolare la lunghezza della coda della lista data: sia n tale lunghezza. La lunghezza della lista è allora n + 1, perché ha un elemento in più rispetto alla sua coda. Procedimento: length lst: se lst e’ vuota, allora riportare 0 altrimenti (* lst ha un elemento in piu’ rispetto alla sua coda *) calcolare la lunghezza n della coda di lst e riportare il valore n+ La definizione è giustificata perché la coda della coda della coda ... della coda di qualsiasi lista è sempre la lista vuota. Quindi prima o poi si arriva al caso base.

ESEMPI:

Lista vuota Lista con un elemento Lista con un elemento Lista con due elementi Lista costruita con un cons (va bene tutto tranne la lista vuota) Lista con almeno un elemento Lista con almeno due elementi ESEMPIO: affrontiamo un problema Problema: Date le ultime X estrazioni del superenalotto (sequenze di 6 numeri compresi tra 1 e 90), determinare i 6 numeri che più probabilmente usciranno alla prossima estrazione. Supponiamo che i “favoriti” sono i numeri estratti con minore frequenza nelle scorse estrazioni! In generale: data una lista contenente liste di interi estrazioni : int list list , dove ogni sottolista contiene DIM elementi, compresi tra 1 e HIGHER , determinare i DIM numeri che occorrono nella lista un minor numero di volte. Problema principale: super: int list list - > int - > int - > int list super estrazioni dim higher :

  • estrazioni è una lista di liste rappresentante le ultime estrazioni, ed è una lista formata da liste di interi! → (int list) list = lista di (liste di interi) perché list si usa in forma postfissa (è una lista di int list, dove ogni int list è una lista di interi)
  • dim è il numero di interi di ogni estrazione (= dimensione di ogni listarella)
  • higher è il massimo numero che può essere estratto (per il range dei numeri estraibili) Riporta i dim numeri che sono stati estratti meno volte

Soluzione intuitiva: conta le occorrenze dei numeri estraibili nelle varie listarelle della lista estrazioni e poi ordinali in base al numero di occorrenze crescente Quello che devo fare è generare una lista di coppia in cui ogni coppia ha un numero estraibile e un numero che rappresenta quante volte è stato estratto

La schedina del superenalotto: sottoproblemi

  1. contare, per ogni numero n da 1 a higher, quante volte n occorre in estrazioni. a) costruire la lista [1;2;...;higher] che contiene tutti i numeri estraibili Sottoproblema upto: int - > int - > int list (si applica a due int: inf e sup e riporta una lista di int) upto n m = [n;n+1;...;m] b) “appiattire” la lista estrazioni, trasformandola in una lista di interi Sottoproblema flatten: ’a list list - > ’a list (trasformare la lista di liste, in una lista di interi) c) “contare” le occorrenze di ciascun elemento di [1;2;...; higher] nella lista flatten estrazioni: Sottoproblema contatutti : ’a list - > ’a list - > (’a * int) list contatutti elementi listona = (riporta la) lista di coppie (ele, n), dove ele è un elemento di elementi (numeri estraibili) e n è il numero di occorrenze di ele in listona. ▪ contare le occorrenze di un elemento in una lista Sottoproblema conta : ’a - > ’a list - > int conta n lista = numero di occorrenze di n in lista poi cosa dovremo fare?
  2. ordinare la lista di coppie ottenute da: contatutti (upto 1 higher) (flatten estrazioni) secondo valori crescenti del secondo elemento (=numero occorrenze) Sottoproblema sort: (’a * ’b) list - > (’a * ’b) list Ora ho una lista di coppie (numero; quante volte occorre) ordinata in modo crescente! E io dovrò prendere le prime 6 coppie, che corrisponderanno ai 6 valori che hanno più probabilità di uscire (visto che hanno avuto il più basso numero di occorrenze)
  3. prendere le prime dim coppie della lista ordinata sort (contatutti (upto 1 higher)(flatten estrazioni)) Sottoproblema take: int - > ’a list - > ’a list take n lista = primi n elementi di lista (o la lista intera se non ce ne sono abbastanza) Ora ho la lista con le mie 6 coppie (o meno di 6) i cui primi valori delle coppie sono i numeri che cerco
  4. dalla lista di dim coppie ottenuta al punto 3: take dim (sort (contatutti (upto 1 higher) (flatten estrazioni))) estrarre la lista formata dai primi elementi di ciascuna coppia: Sottoproblema primi: (’a * ’b) list - > ’a list primi [(x1,y1);...;(xn,yn)] = [x1;...;xn] Abbiamo ora la lista dei 6 numeri che probabilmente usciranno! RECAP PRE-CODICE: (* schedina: super: int list list - > int - > int - > int list super estrazioni dim higher: estrazioni euna lista di liste rappresentante le ultime estrazioni, dim e il numero di interi di ogni estrazione higher eil massimo numero che puo essere estratto Riporta i dim numeri che sono stati estratti meno volte *)

let rec contatutti elementi listona = if elementi=[] (nessun elemento) then [] else (List.hd elementi, conta (List.hd elementi) listona) :: contatutti (List.tl elementi) listona //inserisco la coppia (n, numero occorr) in testa alla lista di coppie che va da n+1 in poi Come miglioro il codice? (così non devo fare Lista.hd due volte) let rec contatutti elementi listona = if elementi=[] then [] else let primo = List.hd elementi in (primo, conta primo listona) :: contatutti (List.tl elementi) listona Versione con pattern marching → NB: non posso usare function perché devo fare il pattern matching proprio su “elementi” quindi devo usare match with let rec contatutti elementi listona = match elementi with [] - > [] | primo::rest - > (primo, conta primo listona) :: contatutti rest listona (* lista non vuota *)

  1. ORDINARE LA LISTA DI COPPIE lo facciamo dopo
  2. prendere le prime dim coppie della lista (* sottoproblema 3: prendere i primi n elementi di una lista ) _(versione con pattern matching) let rec take n = function [] - > []_ //lista vuota | x::rest - > //lista non vuota if n<=0 then [] //se n<= _else x::take(n-1) rest ( versione mia senza pattern matching ma non so se è giusta) ( take: int - > 'a list - > 'a list ) ( take n lista= primi n elementi di lista, o lista stessa se non ce ne sono abbastanza *) let rec take n lista = if n = 0 then [] else List.hd lista :: take (n-1) (List.tl lista)_
  1. primi estrarre la lista formata dai primi elementi di ciascuna coppia: Sottoproblema primi: (’a * ’b) list - > ’a list primi [(x1,y1);...;(xn,yn)] = [x1;...;xn] Let rec primi = function [] - > [] | x::rest - > (fst x) :: primi rest In questo caso, considero x = coppia. Quindi uso il selettore fst x per indicarne il primo elemento Invece che usare il selettore per x, posso passare come pattern (x,y) così x è già il primo elemento Let rec primi = function [] - > [] | (x,y)::rest - > x :: primi rest Meglio ancora, uso una variabile muta perché y non mi serve! Let rec primi = function [] - > [] | (x,)::rest - > x :: primi rest_ ORA DOBBIAMO FARE: FLATTEN E SORT 1b) FLATTEN: “appiattire” la lista estrazioni, trasformandola in una lista di interi Sottoproblema flatten: ’a list list - > ’a list (trasformare la lista di liste, in una lista di interi) Per usare la flatten, devo definire un’altra funzione che mi “concatena” due liste. Perché la flatten non farà altro che prendere tutte le listarelle e concatenarle l’una con l’altra Come posso concatenare due liste? NB:su quale delle due liste faccio la ricorsione? Sulla prima! Perché la seconda non la tocco, semplicemente la attacco a l1. Devo usare il match with perché ho più parametri di ingresso! let rec concat l1 l2 = se l1 è vuota, la concatenazione riporta l match l1 with l1 non è vuota, devo usare x in qualche modo [] - > l2 “concat rest l2” perché con la ricorsione posso assumere | x::rest - > x::(concat rest l2) che qualcun altro sappia risolvere il problema su rest → questa funzione inserisce in testa uno alla volta gli elementi di l1 (a partire dall’ultimo) in l NB: siccome la concatenazione è molto usata in List, abbiamo un operatore ( @ ) che si applica a due liste e riporta la concatenazione delle due liste Definiamo flatten, che si applica ad una lista di liste e riporta una lista semplice (* flatten: ‘a list list - > a’ list *) (* flatten [l1; …; lk] = l1 @ l2 … @ lk *) let rec flatten = function [] - > list | lista::rest - > lista @ flatten rest

ESERCIZIO → Merge Sort (ordinamento per fusione)

Algoritmo: Sia C l’insieme (lista) che si vuole ordinare Si divide l’insieme C in due parti pressappoco uguali. Ciascuno dei due sottoinsiemi viene ordinato, ricorsivamente. Si applica l’algoritmo di fusione alle liste risultanti dalle chiamate ricorsive Casi base → lista vuota o di un solo elemento Caso ricorsivo → per ordinare lst di lunghezza > 1:

  • Si suddivide la lista in due parti di dimensioni pressappoco uguali (±1): siano xs e ys le due liste ottenute
  • Il risultato è la fusione di (mergesort xs) e (mergesort ys): merge (mergesort xs) (mergesort ys) implemento una funzione split che applicata a un ‘a list mi riporta due ‘a list con lunghezze che differiscono al massimo di 1 elemento, e inoltre la loro unione mi deve dare la lista di partenza!
  • Un metodo può essere: uso la funzione take (che prende n elementi) e drop (che toglie i primi n elementi e mi ritorna il resto), in modo tale che (take n lista) @ (drop n lista) = lista
  • Altro metodo: scandisco la listona e mano a mano metto un elemento a destra e uno a sinistra (verrà un ordine diverso ma non mi importa!) (* split: ‘a list - > (a’ list * a’ list) *) riporto una coppia di liste let rec split = function [] - > ([],[]) | [x] - > ([x],[]) lista con un solo elemento | x::y::rest - > lista con almeno due elementi (x::fst(split rest), y:: snd(split rest)) Split rest mi riporta una coppia di liste! Io inserisco x nella prima lista, y nella seconda lista! Come posso migliorare il codice per non calcolare due volte split rest? Usando una let! let rec split = function [] - > ([],[]) | [x] - > ([x],[]) | x::y::rest - > let result = split rest in (x::fst result, y::snd result) Potrei migliorare ulteriormente il codice usando un pattern al posto della let! let rec split = function [] - > ([],[]) | [x] - > ([x],[]) | x::y::rest - > let (prima, seconda) = split rest in (x::prima, y::seconda)

Definiamo minore: let minore (,x) (,y) = x < y //ordina due coppie in base al secondo elemento Ci serve che prenda due coppie perché stavamo lavorando con coppie (numero, occorrenza) Ora facciamo il merge (NB invece che passargli una coppia, gli passo due liste in forma currificata): (* merge = ‘a list - > ‘a list - > ‘a list *) let rec merge prima seconda = match (prima, seconda) with //mi creo la coppia col pattern matching ([],) - > seconda_ //prima lista vuota | (,[]) - > prima_ //seconda lista vuota | (x::rest x, y::rest y) - > //liste non vuote if minore x y //x va per primo nella mia lista ordinata! then x::(merge restx (y::resty)) else y::(merge (x::restx) resty) //y va per primo! Ora possiamo fare il vero e proprio merge sort: let rec msort = function [] - > [] | [x] - > [x] | lista - > //lista con almeno due elementi let (prima,seconda) = split lista in //split mi riporta una coppia di due liste merge (msort prima) (msort seconda) //faccio il merge di due liste ordinate PER ESERCIZIO FAI: QUICK SORT E INSERTION SORT Alcune robe sulle liste:

Il modulo List http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html

List.hd : ’a list - > ’a List.tl : ’a list - > ’a list List.length : ’a list - > int List.flatten : ’a list list - > ’a list List.sort : (’a - > ’a - > int) - > ’a list - > ’a list Sort a list in increasing order according to a comparison function. The comparison function must return 0 if its arguments compare as equal, a positive integer if the first is greater, and a negative integer if the first is smaller. For example, compare is a suitable comparison function. compare: ’a - > ’a - > int

Versione iterativa corretta di upto : anziché incrementare il limite inferiore, decrementiamo quello superiore: (* upto_it : int - > int - > int list ) let upto_it m n = ( aux: int list - > int - > int - > int list aux lista m n = [m;m+1;...;n] @ lista *) let rec aux result m n = if m > n then result else aux (n::result) m (n-1) in aux [] m n VEDIAMO ORA LA FUNZIONE TAKE _Take: versione ricorsiva (* take : int - > ’a list - > ’a list *) let rec take n = function [] - > [] | x::xs - > if n<=0 then [] else x::take (n-1) xs;; Versione iterative? let rtake n lst = let rec aux result n = function [] - > result | x::rest - > if n<=0 then result else aux (x::result) (n-1) rest in aux [] n lst

rtake 4 [1;2;3;4;5;6];;_

- : int list = [4; 3; 2; 1] ➔ Il risultato è rovesciato → posso fare un inserimento in coda così non rovescio nulla Versione iterativa di take con inserimento in coda let take_it n lst = (* aux : ’a list - > int - > ’a list - > ’a list aux result n lst = result @ (take n lst) *) let rec aux result n = function [] - > result | x::rest - > if n<=0 then result else aux (result@[x]) (n-1) rest in aux [] n lst Ma la concatenazione è un’operazione costosa: conviene inserire gli elementi in testa e rovesciare il risultato alla fine ESEMPIO: upto_it 3 5 = aux [] 3 5 = if 3>5 then [] else aux [5] 3 4 = aux [5] 3 4 = aux [4;5] 3 3 = aux [3;4;5] 3 2 = [3;4;5]

Rovesciare una lista Versione ricorsiva (* reverse: ’a list - > ’a list ) let rec reverse = function [] - > [] | x::xs - > (reverse xs) @ [x];; Versione iterativa ( rev: ’a list - > ’a list ) let rev lst = ( revto : ’a list - > ’a list - > ’a list ) ( revto result lst = = (reverse lst) @ result *) let rec revto result = function [] - > result | x::rest - > revto (x::result) rest in revto [] lst ESEMPIO: rev [1;2;3] = revto [] [1;2;3] = revto [1] [2;3] = revto [2;1] [3] = revto [3;2;1] [] = [3;2;1] Versione iterativa di take con il reverse let take_it n lst = (* aux : ’a list - > int - > ’a list - > ’a list ) ( aux result n list = (rev result) @ (take n lst) *) let rec aux result n = function [] - > List.rev result | x::rest - > if n<=0 then List.rev result else aux (x::result) (n-1) rest in aux [] n lst ➔ ‘Ste ultime robe iterative riascoltatele perché non ci ho capito granchè

Esercizio Si consideri la seguente dichiarazione: let rec assoc k lista = function [] - > raise NotFound | (k1,v)::rest - > if k = k1 then v else assoc k rest La dichiarazione non è corretta. Quale errore viene segnalato da OCaml e perché? vengono passati tutti e due i parametri esplicitamente (prima dell’uguale) quindi OCaml capisce che assoc è una funzione che riceve k e lista e riporta una funzione! Ma non è possibile che assoc mi riporti una volta un v (se k=k1) e una volta una funzione (assoc k rest). Quindi se usiamo la parola chiave function, non dobbiamo dare un nome all’ultimo argomento (=non dobbiamo passargli lista esplicitamente) OPPURE possiamo passargli k e lista esplicitamente ma dopo =function dobbiamo usare un match lista with

Inserimento

Problema: data una chiave k, un valore v e una lista associativa assoc_list, riportare la lista che si ottiene inserendo la coppia (k,v) in assoc_list – sostituendo (o sovrascrivendo) l’eventuale elemento già esistente con chiave k inserisci: ’a - > ’b - > (’a * ’b) list - > (’a * ’b) list La proprietà fondamentale dell’inserimento è in relazione con la ricerca: si deve avere che assoc k (inserisci k v assoclist) = v Sostituire effettivamente un’eventuale coppia (k,v’) già presente in assoc_list è “costoso”: occorre controllare tutta la lista. Quindi non mi preoccupo che ci siano doppioni, tanto quando cerco un elemento, il primo che incontro va bene. Quindi poiché la ricerca riporta il primo valore trovato associato alla chiave della ricerca, è sufficiente inserire la nuova coppia prima delle altre (con un semplice cons) let inserisci k v assoc_list = (k,v)::assoc_list Questo però significa che la cancellazione è un po’ più complicata

Cancellazione

Problema: data una chiave k e una lista associativa assoc_list, riportare la lista che si ottiene cancellando da assoc_list tutte le coppie con chiave k (potrebbero essercene diverse). Se non c’è nessuna coppia con chiave k, riportare assoc_list stessa cancella : ’a - > (’a * ’b) list - > (’a * ’b) list let rec cancella k = function [] - > [] | (k’,v)::rest - > if k=k’ then cancella k rest else (k’,v)::cancella k rest (k’,v)::cancella k rest significa che la coppia (k’,v), che non devo cancellare, la lascio lì nella lista che restituirò al ritorno dalla ricorsione (=la lista senza i valori cancellati)

Rappresentazione di tipi astratti di dati

Un tipo astratto di dati è costituito da un insieme di oggetti A e un insieme di operazioni su tali oggetti Per rappresentare un tipo astratto A:

  1. Determinare un tipo concreto T tale che: a. ogni oggetto di A abbia almeno un “rappresentante” in T; b. elementi distinti di A abbiano rappresentanti distinti in T (non vengono confusi);
  2. Implementare ogni operazione F su A mediante un’operazione P su T in modo tale che la rappresentazione “conservi le operazioni”: se v1, ..., vn sono valori di T che rappresentano, rispettivamente, gli oggetti a1, ..., an di A, allora P(v1, ..., vn) è un rappresentante di F(a1, ..., an) Quindi “operare sui rappresentanti” e poi determinare l’oggetto rappresentato dal valore ottenuto fornisce lo stesso risultato che si ottiene operando sugli oggetti di A stessi. Siano A = (A, f1,... , fn) e B = (B, g1,... , gn) due strutture algebriche dello stesso tipo (per ogni i = 1... n, fi e gi hanno lo stesso numero di argomenti). Una rappresentazione di A (tipo astratto) in B (tipo concreto) è un’applicazione (funzione parziale) ϕ : B → A (ϕ(b) = a: b rappresenta a, quindi ogni b ∈ B rappresenta al massimo un a ∈ A) tale che:
  • per ogni a ∈ A esiste b ∈ B tale che ϕ(b) = a (ϕ è suriettiva);
  • per ogni i = 1... n, e per ogni b1,... , bn di B per cui ϕ è definita, si ha: ϕ(gi(b1,... , bn)) = fi(ϕ(b1),... , ϕ(bn)) (ϕ è un omomorfismo) (Leggere il paragrafo 2.3.3 delle dispense – o il 5.1.2 del libro di testo NB* ∈ (appartenenza) ∪ (unione) ∩ (intersezione) \ (complemento)

Rappresentazione di insiemi finiti mediante liste

Tipo astratto “ insieme finito di elementi dello stesso tipo ”, con le operazini: ∈, ∪, ∩ e \ NB* Tipo concreto: liste. Una lista lst rappresenta l’insieme che contiene esattamente gli elementi di lst.

  • Ogni insieme finito di elementi di tipo ’a ha almeno un “rappresentante” in ’a list. (Quanti?)
  • Insiemi distinti hanno rappresentanti distinti in ’a list: ogni lista rappresenta un unico insieme. Si devono definire funzioni che rappresentino le operazioni sul tipo astratto: mem : applicata a un elemento x e la rappresentazione di un insieme S, determina se x ∈ S (è un predicato). union : applicata a due liste che rappresentano insiemi S1 e S2, riporta una rappresentazione di S1 ∪ S2. intersect : applicata a due liste che rappresentano insiemi S1 e S2, riporta una rappresentazione di S1 ∩ S2. setdiff : applicata a due liste che rappresentano insiemi S1 e S2, riporta una rappresentazione di S1 \ S2. NB: qual è la differenza tra insiemi e liste? Le liste possono avere ripetizioni e l’ordine degli elementi è importante! (negli insiemi queste cose non accadono) quindi un insieme può avere più rappresentanti! ES: l’insieme {1,2} può essere rappresentato dalle liste [1,2] [2,1] [1,1,2,2] [2,1,2,2,2,1] ecc Qual è l’insieme che ha un solo rappresentante? L’insieme vuoto → lista vuota

ESEMPIO Somma di sottoinsiemi Problema: Dato un insieme S di numeri interi positivi e un intero N, determinare un sottoinsieme Y di S tale che la somma degli elementi di Y sia uguale a N. Esempio: S = {8, 5, 1, 4}, N = 9. Aggiungiamo una componente alla volta. Spazio di ricerca delle soluzioni: albero etichettato da sottoinsiemi di S Foglie: situazioni non ulterirmente espandibili; alcune di esse possono rappresentare una soluzione (successo) altre no (fallimento). L’esplorazione dello spazio di ricerca avviene in profondità NB: quest’albero di scelta non rappresenta l’algoritmo che implementerò! Ma solo la mia mappa concettuale Parto dalla lista vuota, posso aggiungere uno dei 4 elementi, poi posso aggiungerne uno dei tre rimasti ecc.. Algoritmo La ricerca avviene per stadi, ad ogni stadio della ricerca:

  • una soluzione parziale Solution
  • un insieme Altri di elementi tra cui scegliere Inizialmente: Solution = ∅, Altri = S
  • Se la somma degli elementi di Solution è maggiore di N: fallimento.
  • Se la somma degli elementi di Solution è uguale a N: successo con Solution.
  • Altrimenti (somma degli elementi di Solution minore di N): o Se Altri è vuoto, allora fallimento. o se Altri non è vuoto, scegliere un elemento x di Altri. Alternativa: ad ogni step considero la x, ce la metto o non ce la metto? ▪ mettere x nella soluzione con un try with: cercare una soluzione aggiungendo x a solution ossia {x} ∪ Solution e togliendo x da Altri ossia Altri − {x} Se questa soluzione è buona ho finito! E questa è la mia soluzione ▪ altrimenti non mettere x nella soluzione: cercare una soluzione con Solution che rimane tale e quale ma togliendo la x da Altri ossia facendo Altri − {x} CODICE: 1:13h lez 26. let subset_search set n = //set è l’insieme degli elementi da cui scegliere ed n let rec aux solution altri = //aux è funz ausiliaria definita localmente let somma = sumof solution in //solution è la soluzione parziale (* caso di fallimento ) if somma > n then raise NotFound else if somma=n then solution ( terminazione con successo ) else match altri with //meglio il pattern matching che i selettori [] - > ( non si possono aggiungere altri elementi ) raise NotFound | x::rest - > ( proviamo ad aggiungere x, se troviamo una soluzione, bene, altrimenti proviamo senza x *) try aux (x::solution) rest with NotFound - > aux solution rest in aux [] set //leggiti tutto il codice nel pacchetto 5

Attraversamento di un LABIRINTO Rappresentazione della struttura mediante:

  • dimensione della matrice (nxn)
  • lista delle caselle contenenti un mostro let dim = 5 let mostri = [(0,2);(1,1);(1,3);(2,3);(3,0);(4,2)] NB: il labirinto è quadrato! E in alcune caselle può essere presente un mostro! Devo trovare un percorso che dalla casella in entrata va nella casella in uscita senza passare per mostri Restrizione in più: per non tener traccia delle caselle in cui siamo già passati, mettiamo il vincolo che da una casella in una certa colonna possiamo soltanto andare verso destra (in una delle tre caselle adiacenti). ES: da (1,0) posso andare (0,1) (1,1) e (2,1) È come se fosse un albero ternario in cui ogni nodo ha al massimo 3 figli Partiamo per esempio dalla casella (2,0) NB: ho anche i parametri ingresso e uscita Dobbiamo identificare quando terminiamo con successo, quando con fallimento e quando andiamo avanti Fallimento - > se nella casella c’è il mostro (per verificarlo devo vedere se la casella c è nella lista mostri) (attraverso la funzione List.mem) Successo - > se c=uscita ho finito con successo e qual è la soluzione? Una lista di caselle (coppie di interi) che rappresentano il percorso che ho fatto per uscire! Se c non contiene il mostro e non è uguale all’uscita, entra in gioco il backtracking! Ho diverse alternative: c avrà 0,1 o 2 vicini quindi:
  • sottoproblema: determinare i vicini della casella ES: sia vic = vicini casella o se non ho vicini fallisco! Non posso continuare o se ho un solo vicino, se non fallisco mi ci rimetto in testa (perché mi riporta un cammino) o se ho due vicini, vado su x e se mi va bene ok, altrimenti chiamo y. Chi mi riporta un cammino, mi ci metto in testa → uso tutti try with! o Se ho tre vicini, stessa cosa ma con tutti e Implementazione: in_labirinto verifica se una casella è nel labirinto

0 Mostro 1 Mostro Mostro 2 Mostro 3 Mostro 4 Mostro (0,1) (1,0) (1,1) Mostro (2,1)