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.