Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
pp:lplcut [2019/05/06 10:24]
dmihai created
pp:lplcut [2019/05/06 14:30] (current)
dmihai [Colectarea rezultatelor]
Line 1: Line 1:
-===== TODO =====+===== Cuts =====
  
 Scopul laboratorului:​ Scopul laboratorului:​
  
-  - TODO +  - aprofundarea mecanismului de căutare din prolog 
-  - TODO+  - înțelegerea cut-urilor ca mecanism de control al căutării 
 +  - distincția între "green cuts" și "red cuts"​ 
 +  - metode de colectare a rezultatelor
  
 ==== Cut ==== ==== Cut ====
  
-=== Green cut ===+Pentru a demonstra goal-urile necesare, Prolog folosește backtracking pentru a explora toate posibilitățile,​ rezultând într-un //arbore de căutare//. Uneori asta duce la comportamente neintuitive:​
  
-=== Red cut ===+<code prolog>​ 
 +min(X, Y, X) :- X < Y. 
 +min(X, Y, Y). 
 +</​code>​ 
 + 
 +<​code>​ 
 +?- min(1, 2, 1). 
 +true ; 
 +false. 
 + 
 +?- 
 +</​code>​ 
 + 
 +Putem încerca să rezolvăm problema, explicitând condiția din cea de-a doua clauză: 
 +<code prolog>​ 
 +min(X, Y, X) :- X < Y. 
 +min(X, Y, Y) :- X >Y. 
 +</​code>​ 
 + 
 +Dar tot obținem un al doilea rezultat ''​false''​. 
 + 
 +<​code>​ 
 +?- min(1, 2, 1). 
 +true ; 
 +false. 
 + 
 +?- 
 +</​code>​ 
 + 
 +Putem porni un trace în program pentru a vedea ce se întâmplă:​ 
 + 
 +<​code>​ 
 +?- trace. 
 + 
 +[trace] ​ ?- min(1, 2, 1). 
 +   Call: (8) min(1, 2, 1) ? creep 
 +   Call: (9) 1<2 ? creep 
 +   Exit: (9) 1<2 ? creep 
 +   Exit: (8) min(1, 2, 1) ? creep 
 +true ; 
 +   Redo: (8) min(1, 2, 1) ? creep 
 +   Fail: (8) min(1, 2, 1) ? creep 
 +false. 
 + 
 +[trace] ​ ?- 
 +</​code>​ 
 + 
 +După ce obținem un răspuns positiv din prima clauză, Prolog încearcă să găsească și alte soluții, folosind a doua ramură. Ajunge astfel la goal-ul ''​min(X,​ Y, Y)''​ care nu poate fi satisfăcut,​ deoarece nu există o unificare posibilă, deci căutarea eșuează. Am vrea să avem control asupra execuției procedurale,​ eliminând ramuri din arborele de căutare (în cazul nostru, am vrea ca după ce am aflat că un element se găsește în listă, să ne oprim). 
 + 
 +Pentru asta avem predicatul [[http://​www.swi-prolog.org/​pldoc/​doc_for?​object=!/0|!/0]] (numit "​cut"​). Acesta poate fi mereu satisfăcut,​ dar are un efect lateral: împiedică resatisfacerea părții din stânga, inclusiv **clauza** (amintiți-vă că o clauză este practic o "​linie",​ iar un predicat e o colecție de una sau mai multe clauze. Predicatul ''​elem''​ are două clauze). Astfel, putem rezolva problema noastră: 
 + 
 +<code prolog>​ 
 +min(X, Y, X) :- X < Y, !. 
 +min(X, Y, Y) :- Y >X. 
 +</​code>​ 
 + 
 +<​code>​ 
 +[trace] ​ ?- min(1, 2, 1). 
 +   Call: (8) min(1, 2, 1) ? creep 
 +   Call: (9) 1<2 ? creep 
 +   Exit: (9) 1<2 ? creep 
 +   Exit: (8) min(1, 2, 1) ? creep 
 +true. 
 + 
 +[trace] ​ ?- 
 +</​code>​ 
 + 
 +Când primește goal-ul ''​min(1,​ 2, X)'',​ prolog încearcă, de sus în jos, toate clauzele predicatului ''​min''​. Pentru că reușește unificarea cu substituția ''​{X = 1, Y = 2}'',​ alege prima clauză. Aceasta este o regulă, deci trebuie încercată satisfacerea corpului acesteia, adică ''​X < Y, !''​. ''​1 < 2''​ este adevărat, iar predicatul ''​!''​ reușește și deci și clauza curentă, iar prolog ne raportează ''​true'';​ dar acum nu mai poate să încerce o altă clauză a predicatului ''​min''​. 
 + 
 +Putem folosi predicatul "cut" pentru a altera rezultatul unei interogări,​ dar și doar cu scopul de a face o interogare mai eficientă, având același rezultat. Astfel distingem între "green cuts" și "red cuts"​. 
 + 
 +=== Green cuts === 
 + 
 +Un "green cut" este un "​cut"​ care are scopul de a face un program mai eficient, fără a-i schimba outputul. Cut-ul folosit mai sus are această proprietate. Fără acesta, prolog ar verifica și cea de-a doua ramură, în mod inutil. În principal, un "green cut" ar trebui folosit pentru clause mutual-exclusive:​ dacă una din ele e adevărate, cealălalte nu poate fi. O carecteristică a acestor cut-uri este că eliminarea lor din program, nu schimbă rezultatul. 
 + 
 +=== Red cuts === 
 + 
 +Definiția predicatului ''​min''​ de mai sus, poate părea nesatisfăctoare. Avem în ambele clauzele o comparație explicită a argumentelor. Intuitiv, observăm că cele două condiții sunt mutual exclusive, deci ar fi suficient ca doar una să fie explicitată. 
 + 
 +<code prolog>​ 
 +min(X, Y, X) :- X < Y, !. 
 +min(X, Y, Y). 
 +</​code>​ 
 + 
 +La prima vedere, predicatul pare corect: 
 + 
 +<​code>​ 
 +?- min(1, 2, X). 
 +X = 1. 
 + 
 +?- min(2, 1, X). 
 +X = 1. 
 + 
 +?- 
 +</​code>​ 
 + 
 +Însă putem observa că atunci când toate variabilele sunt instanțiate,​ comportamentul nu e cel dorit: 
 + 
 +<​code>​ 
 +?- min(1, 2, 2). 
 +true. 
 + 
 +?- 
 +</​code>​ 
 + 
 +Pentru că nu poate aborda prima clauză (nu există nicio substituție posibilă, ''​X''​ ar trebui să fie și ''​1''​ și ''​2''​),​ prolog trece la a doua. Cu substituția ''​{X = 1, Y = 2}'',​ goal-ul reușește și prolog raportează ''​true''​. 
 + 
 +O soluție este să întârziem unificarea variablei rezultat, până după comparație:​ 
 + 
 +<code prolog>​ 
 +min(X, Y, Z) :- X < Y, !, X = Z. 
 +min(X, Y, Y). 
 +</​code>​ 
 + 
 +<​code>​ 
 +?- min(1, 2, 2). 
 +false. 
 + 
 +?- 
 +</​code>​ 
 + 
 +Inițial prolog găsește substituția ''​{X = 1, Y = 2, Z = 2}''​. Comparația ''​X < Y''​ reușește (deoarece ''​1 < 2''​),​ ''​!''​ reușește, dar unificarea ''​X = Z''​ nu (ambele sunt variabile instanțiate la valori diferite). Din cauza cut-ului, prolog nu poate să încerce a doua clauză, deci rezultatul e ''​false''​.
  
-==== Negation as failure ====+Fără acel cut, predicatul ar funcționa ca variante dinainte (cea fără variabila ''​Z''​). Astfel de cut-uri, care alterează comportamentul programului,​ se numesc "red cuts". O recomandare comună e că ar trebui folosite cu grijă, pentru că pot îngreuna înțelegerea codului.
  
-==== Colectare rezultate ​====+==== Colectarea rezultatelor ​====
  
 Fie următoarea bază de cunoștiințe genealogice:​ Fie următoarea bază de cunoștiințe genealogice:​
Line 71: Line 194:
 </​code>​ </​code>​
  
-Dacă nu există niciun rezultat, ''​findall''​ generează o listă vidă:+Dacă nu există niciun rezultat, ''​findall/3''​ generează o listă vidă:
  
 <​code>​ <​code>​
Line 91: Line 214:
 === bagof === === bagof ===
  
-[[http://​www.swi-prolog.org/​pldoc/​man?​predicate=bagof/​3|bagof/​3]] este un predicat asemănător cu ''​findall'',​ având aceeași semnificație a parametrilor. Diferența dintre cele două este vizibilă atunci când introducem o variabilă ​în goal, care nu apare în template. ​+[[http://​www.swi-prolog.org/​pldoc/​man?​predicate=bagof/​3|bagof/​3]] este un predicat asemănător cu ''​findall/3'',​ având aceeași semnificație a parametrilor. Diferența dintre cele două este vizibilă atunci când introducem ​în goal o variabilă care nu apare în template. ​
  
-<​code ​prolog>+<​code>​
 ?- findall(C, parent(P, C), L). ?- findall(C, parent(P, C), L).
 L = [odin, vili, ve, thor, baldr, borr, odin, vili, ve|...]. L = [odin, vili, ve, thor, baldr, borr, odin, vili, ve|...].
Line 100: Line 223:
 </​code>​ </​code>​
  
-Observăm că ''​findall''​ ne întoarce o singură listă cu toate rezultatele posibile. În unele cazuri, ar fi util ca, pentru fiecare părinte ''​P'',​ să primim o altă listă. Pentru asta folosim ''​bagof'':​+Observăm că ''​findall/3''​ ne întoarce o singură listă cu toate rezultatele posibile. În unele cazuri, ar fi util ca, pentru fiecare părinte ''​P'',​ să primim o altă listă. Pentru asta folosim ''​bagof'':​
  
-<​code ​prolog>+<​code>​
 ?- bagof(C, parent(P, C), L). ?- bagof(C, parent(P, C), L).
 P = bestla, P = bestla,
Line 120: Line 243:
 </​code>​ </​code>​
  
-Rezultatele sunt generate în stilul clasic, necesitând '';''​ interactiv. Putem apoi să le colectăm folosind din nou ''​bagof'':​+Rezultatele sunt generate în stilul clasic, necesitând '';''​ interactiv. Putem apoi să le colectăm folosind din nou ''​bagof/3'':​
  
 <​code>​ <​code>​
Line 129: Line 252:
 </​code>​ </​code>​
  
-<​note>​ +Ca urmare a felului de funcționareo diferență notabilă între ​''​findall/3'' ​și ''​bagof/3'' ​este că, atunci când goal-ul nu poate fi satisfăcut,​ ''​findall/​3''​ reușește, generând o listă vidă, iar ''​bagof/​3''​ eșuează. 
-În ultima interogarenu contează dacă predicatul folosit în exterior este ''​bagof'' ​sau ''​findall''​. Goal-ul de demonstrat (''​bagof(C,​ parent(P, C), L)''​nu conține variabile "din afară"​+ 
-</note>+<​code>​ 
 +?- findall(C, parent(thor, C), L)
 +L = []. 
 + 
 +?- bagof(C, parent(thor,​ C), L). 
 +false. 
 + 
 +?- 
 +</code>
  
 === setof === === setof ===
 +
 +Fie următorul predicat, pentru a stabili care două persoane au un copil în comun:
 +
 +<code prolog>
 +common_child(F,​ M) :- father(F, C), mother(M, C).
 +</​code>​
 +
 +Am vrea să generăm o listă cu toate persoanele care au un copil cu Odin. Ne folosim de predicatele ''​bagof/​3''​ și ''​common_child/​2'':​
 +
 +<​code>​
 +?- bagof(M, common_child(odin,​ M), L).
 +L = [jorth, frigg].
 +
 +?-
 +</​code>​
 +
 +Obținem rezultatul dorit. Ce se întâmplă însă dacă două persoane au în comun mai mulți copii?
 +
 +<​code>​
 +?- bagof(M, common_child(borr,​ M), L).
 +L = [bestla, bestla, bestla].
 +
 +?-
 +</​code>​
 +
 +Rezultatul nu este ideal, deoarece //conține duplicate//​.
 +
 +[[http://​www.swi-prolog.org/​pldoc/​doc_for?​object=setof/​3|setof/​3]] este un predicat asemănător cu ''​bagof'',​ având aceeași semnificație a parametrilor,​ dar care generează o listă //sortată, fără duplicate// (mai multe informații despre ce implică sortarea, puteți găsi [[http://​www.swi-prolog.org/​pldoc/​man?​section=standardorder|aici]]).
 +
 +<​code>​
 +?- setof(M, common_child(odin,​ M), L).
 +L = [frigg, jorth].
 +
 +?- setof(M, common_child(borr,​ M), L).
 +L = [bestla].
 +
 +?-
 +</​code>​
 +
 +În rest, ''​setof/​3''​ se comportă ca ''​bagof/​3'';​ pentru variabilele din goal care nu apar în template generează soluții separate și eșuează dacă nu există niciuna:
 +
 +<​code>​
 +?- setof(M, common_child(F,​ M), L).
 +F = borr,
 +L = [bestla] ;
 +F = odin,
 +L = [frigg, jorth].
 +
 +?- setof(M, common_child(thor,​ M), L).
 +false.
 +
 +?-
 +</​code>​
 +
 +<​note>​
 +În mod default, swi-prolog trunchiază listele rezultat, afișând doar primele elemente și ''​...''​ pentru restul. Pentru a schimba acest comportament folosiți interogarea ''​set_prolog_flag(answer_write_options,​[max_depth(0)]).'':​
 +
 +<​code>​
 +?- findall(P, parent(P, C), L).
 +L = [bestla, bestla, bestla, jorth, frigg, buri, borr, borr, borr|...].
 +
 +?- set_prolog_flag(answer_write_options,​[max_depth(0)]).
 +true.
 +
 +?- findall(P, parent(P, C), L).
 +L = [bestla,​bestla,​bestla,​jorth,​frigg,​buri,​borr,​borr,​borr,​odin,​odin].
 +</​code>​
 +
 +Soluție alternativă găsiți [[https://​stackoverflow.com/​a/​36948699|aici]].
 +</​note>​
  
 ==== Exerciții ==== ==== Exerciții ====
 +
 +  - Definiți predicatul ''​cartesian(L1,​ L2, R)''​ care construiește [[http://​mathworld.wolfram.com/​CartesianProduct.html|produsul cartezian]] al ''​L1''​ cu ''​L2''​.
 +  - Definiți predicatul ''​union(L1,​ L2, R)''​ care construiește reuniunea a două mulțimi codificate ca liste.
 +  - Definiți predicatul ''​intersection(L1,​ L2, R)''​ care construiește interesecția a două mulțimi codificate ca liste.
 +  - Definiți predicatul ''​diff(L1,​ L2, R)''​ care construiește diferenta a două mulțimi codificate ca liste.
 +  - Definiți predicatul ''​pow(S,​ R)''​ care construiește [[http://​mathworld.wolfram.com/​PowerSet.html|power set-ul]] multimii ''​S''​.
 +  - Definiți predicatul ''​perm(S,​ R)''​ care generează toate permutările lui ''​S''​.
 +  - Definiți predicatul ''​ar(K,​ S, R)''​ care generează toate aranjamentele de dimensiune ''​K''​ cu elemente luate din ''​S''​.
 +  - Definiți predicatul ''​comb(K,​ S, R)''​ care generează toate combinările de dimensiune ''​K''​ cu elemente luate din ''​S''​.
  
 ==== Recommended Reading ==== ==== Recommended Reading ====
 +
 +  * [[https://​mitpress.mit.edu/​books/​art-prolog-second-edition|The Art of Prolog - II. The Prolog Language - 11. Cuts and Negation]]
 +  * [[http://​www.learnprolognow.org/​lpnpage.php?​pagetype=html&​pageid=lpn-htmlch5|Learn Prolog Now! - Chapter 5 Arithmetic]]
 +  * [[http://​www.learnprolognow.org/​lpnpage.php?​pagetype=html&​pageid=lpn-htmlch6|Learn Prolog Now! - Chapter 6 More Lists]]
 +  * [[http://​www.learnprolognow.org/​lpnpage.php?​pagetype=html&​pageid=lpn-htmlch10|Learn Prolog Now! - Chapter 10 Cuts and Negation]]
 +  * [[http://​www.learnprolognow.org/​lpnpage.php?​pagetype=html&​pageid=lpn-htmlch11|Learn Prolog Now! - Chapter 11 Database Manipulation and Collecting Solutions]]
 +