Differences
This shows you the differences between two versions of the page.
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ționare, o 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 interogare, nu 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]] | ||
+ |