Ricorsione in Prolog

Transcript

Ricorsione in Prolog
RICORSIONE IN PROLOG
Esercitazioni per il corso di
Logica ed Intelligenza Artificiale
a.a. 2013-14
Vito Claudio Ostuni
Il Prolog non fornisce alcun tipo di costrutto sintattico per
l’iterazione ( come i classici cicli utilizzati nella
programmazione imperativa).
Lo strumento maggiormente utilizzato in Prolog per la
realizzazione di strutture di iterazione e controllo è la
ricorsione.
La ricorsione viene usata principalmente in due casi:
•per percorrere “catene” di relazioni tra oggetti
•per operare efficacemente sulle liste
Inizialmente analizzeremo il primo, propedeutico alla
comprensione del secondo. Esempi classici sono le relazioni
“transitive”:
-se Antonio è fratello di Bruno, e Bruno è fratello di Carlo, allora deve essere
vero anche che Antonio è fratello di Carlo.
-se il punto A è collegato al punto B e il punto B è collegato al punto C, allora A
è collegato indirettamente a C.
-se le chiavi sono nel cassetto, e il cassetto è nel mobile, indirettamente il
mobile contiene le chiavi.
Per sapere se queste affermazioni sono vere, quindi, occorre
verificare ricorsivamente le relazioni di “fratello di”, “collegato a”
e “contenuto in”, creando una regola che tenga conto della
transitività di queste ultime.
Una regola ricorsiva in Prolog presenta sempre una boundary
condition, condizione che consente di evitare che una funzione
venga richiamata all’infinito, e la regola ricorsiva vera e propria
che richiama se stessa, ovvero si ritrova la testa della regola
anche nel corpo.
La boundary condition dev’essere sempre scritta prima della
regola ricorsiva relativa, proprio per evitare che cicli all’infinito.
is_contained_in(T1,T2):- location(T1,T2).
boundary condition
is_contained_in(T1,T2):- location(X,T2), is_contained_in(T1,X).
location(desk,office).
location(computer,office).
location(flashlight,desk).
location(envelope,desk).
location(stamp,envelope).
location(key,envelope).
Vogliamo verificare che la chiave si trovi nell’ufficio.
is_contained_in(T1,T2):- location(T1,T2).
is_contained_in(T1,T2):- location(X,T2), is_contained_in(T1,X).
La prima regola, detta boundary condition, determina la fine della
ricorsione. Vera quando la relazione diretta“location” è verificata.
Altrimenti, l’interprete Prolog passa alla definizione successiva di
is_contained_in, che è ricorsiva. (può essere visto come un if-else).
La seconda regola trova un oggetto X contenuto in T2 e richiama la
stessa regola su T1 e X. In pratica cerca di “risalire” da T2 verso T1, finchè
non trova, eventualmente, un oggetto che contiene direttamente T1.
is_contained_in(T1,T2):- location(T1,T2).
is_contained_in(T1,T2):- location(X,T2), is_contained_in(T1,X).
Questa regola è ottimizzata per query di tipo
is_contained_in(X, office)
Trova oggetti contenuti nell’ufficio e ripete la query su questi.
Quindi si procede da T2 verso T1.
Restituisce tutti gli oggetti contenuti nell’ufficio (desk, computer,
flashlight, …).
Possiamo ottimizzare la query per il caso contrario in cui ci
troviamo nella situazione is_contained_in(key, X)
is_contained_in(T1,T2):- location(T1,T2).
is_contained_in(T1,T2):- location(T1,X), is_contained_in(X,T2).
Così facendo la query is_contained_in(key, X)
potrà restituire tutti gli oggetti in cui è contenuta la chiave
(envelope, desk e office). Stiamo modellando esattamente lo
scenario duale al precedente.
Ciò che varia è solo l’efficienza della query in base a come è
strutturata la query rispetto alla regola ricorsiva presente nella KB.
ALBERO DI RICERCA PER QUERY RICORSIVE
is_contained_in(T1,T2):-location(T1,T2).
is_contained_in(T1,T2):-location(X,T2),is_contained_in(T1,X).
ottimizzato per query di tipo is_contained_in(X, office)
is_contained_in(T1,T2).
.
location(T1,T2).
location(X,T2)
is_contained_in(T1,X).
.
location(T1,X).
location(Y,X)
is_contained_in(T1,X).
.
parent(giuseppe,antonio).
parent(anna,antonio).
parent(mario,giuseppe).
parent(franca,giuseppe).
parent(luca,anna).
parent(teresa,anna).
Creiamo due regole, una per verificare se X discende da Y ed un’altra per
verificare che X sia un antenato di Y.
descendant(X,Y) :- parent(Y,X).
descendant(X,Y) :- parent(Y,F),descendant(X,F).
Oppure
descendant(X,Y) :- parent(Y,X).
descendant(X,Y) :- parent(F,X),descendant(F,Y).
Ancestor(X,Y) :- parent(X,Y).
Ancestor(X,Y) :- parent(G,Y),ancestor(X,G).
Oppure
Ancestor(X,Y) :- parent(X,Y).
Ancestor(X,Y) :- parent(X,F),ancestor(F,Y).
arc(d,c).
arc(a,d).
arc(b,a).
arc(b,d).
Definiamo una regola che ci da il
percorso tra due nodi
path(X,Y) :-arc(X,Y).
path(X,Y) :-arc(X,Z),path(Z,Y).
Oppure
path(X,Y) :-arc(X,Y).
path(X,Y) :-arc(X,Z),path(Z,Y).
Potenza Z=X^y
power(X,1,X).
power(X,Y,Z):- I is Y-1, power(X,I,Z1), Z is Z1*X.
La regola viene invocata su valori di y via via decrescenti.
Quando questo arriva ad 1, la boundary condition stabilisce che Z=X1=Z.
Questo viene quindi viene poi moltiplicato per X, Y-1 volte.
Ovviamente l’uso del ; nella query al Prolog è erroneo in quanto la regola
ammette un unico risultato.