Sunteți pe pagina 1din 136

A. M.

Florea

B. Dorohonceanu

C. Frncu

Programare n Prolog
ndrumar de laborator la disciplina Elemente de Inteligen Artificial

UNIVERSITATEA POLITEHNICA BUCURETI 1997

A. M. Florea

B. Dorohonceanu

C. Frncu

Programare n Prolog
ndrumar de laborator la disciplina Elemente de Inteligen Artificial

UNIVERSITATEA POLITEHNICA BUCURETI

Prefa
ndrumarul este destinat studenilor facultii de Automatic i Calculatoare, secia Calculatoare, care urmeaz cursul Elemente de Inteligen Artificial. Lucrarea este structurat n dou pri. Prima parte prezint limbajul Prolog, unul dintre limbajele prefereniale pentru implementarea aplicaiilor de inteligen artificial dar care, n ultimul timp, este folosit pentru dezvoltarea de aplicaii n diverse alte domenii. Caracterul logic, descriptiv al limbajului permite o exprimare compact i elegant a prelucrrilor, n special a celor simbolice, i un efort redus de implementare pentru o clas larg de probleme. Partea a doua a ndrumarului trateaz tehnici de realizare a programelor Prolog, att a programelor ce implementeaz algoritmi generali, cum ar fi prelucrarea listelor, cutare, sortare, ct i a programelor bazate pe algoritmi specifici inteligenei artificiale. Prezentarea materialului permite nvarea i familiarizarea cu limbajul Prolog i cu tehnicile specifice de construire a programelor eficiente, lucrarea fiind astfel deschis oricrui cititor care este interesat n acest limbaj. Parcurgerea lucrrii necesit cunotine de structuri de date i tehnici de programare i este facilitat de citirea crii Elemente de Inteligen Artificial, autor A. M. Florea, n special pentru nelegerea de substan a capitolelor destinate dezvoltrii n Prolog a aplicaiilor de inteligen artificial, cum ar fi rezolvarea problemelor prin cutri neinformate sau euristice, teoria jocurilor sau sistemele bazate pe reguli de producie. n cei apte ani de existen a cursului Elemente de Inteligen Artificial o serie de asisteni i-au adus contribuia la desfurarea cu succes a laboratorului asociat, o bun parte dintre ei urmnd n prezent studii de doctorat n strintate. Pentru activitatea depus, dorim s mulumim pe acest cale urmtorilor colaboratori: Iustin Roca (Rochester University), Marin Simina (Santa Barbara University), Octav Popescu (Carnegie Mellon University), Mirela Damian (Iowa University), Simona Costinescu (Universitatea Politehnica), Alexandra Cristea (Technical University of Tokyo), tefan Bruda (Centrul de Cercetri n Inteligen Artificial al Academiei Romne). Autorii Bucureti, 1997

Partea I Limbajul Prolog


nceputul programrii logice poate fi atribuit lui R. Kowalski i A. Colmerauer i se situeaz la nceputul anilor '70. Kowalski a plecat de la o formul logic de tipul: S1 S2 ... Sn S care are, n logica cu predicate de ordinul nti semnificaia declarativ conform creia S1 S2 ... Sn implic S, adic dac S1 i S2 ... i Sn sunt fiecare adevrate atunci i S este adevrat, i a propus o interpretare procedural asociat. Conform acestei interpretri, formula de mai sus poate fi scris sub forma: S dac S1 i S2 ... i Sn i poate fi executat ca o procedur a unui limbaj de programare recursiv, unde S este antetul procedurii i S1, S2, ... Sn corpul acesteia. Deci, pe lng interpretarea declarativ, logic, a unei astfel de formule, formula poate fi interpretat procedural astfel: pentru a executa S se execut S1 i S2 ... i Sn.
n aceeai perioad, A. Colmerauer i colectivul lui de cercetare de la Universitatea din Marsilia au dezvoltat un limbaj de implementare a acestei abordri, pe care l-au denumit Prolog, abreviere de la "Programmation et Logique". De atunci i pn n prezent, limbajul Prolog s-a impus ca cel mai important limbaj de programare logic i s-au dezvoltat numeroase implementri, att ale unor interpretoare ct i ale unor compilatoare ale limbajului. Limbajul Prolog este un limbaj declarativ susinut de o component procedural. Spre deosebire de limbajele procedurale, cum ar fi C sau Pascal, n care rezolvarea problemei este specificat printr-o serie de pai de execuie sau aciuni, ntr-un limbaj declarativ problema este specificat prin descrierea universului problemei i a relaiilor sau funciilor existente ntre obiecte din acest univers. Exemple de astfel de limbaje sunt cele funcionale, de exemplu Lisp, Scheme, ML, i cele logice, de exemplu Prolog. Dei iniial a fost gndit pentru un set restrns de probleme, Prolog a devenit cu timpul un limbaj de uz general, fiind o unealt important n aplicaiile de inteligen artificial [CM84, Bra88, SS86]. Pentru multe probleme, un program Prolog are cam de 10 ori mai puine linii dect echivalentul lui n Pascal. n 1983, cercettorii din Japonia au publicat un plan ambiios de creare a unor calculatoare de generaia a 5-a pentru care Prolog era limbajul de asamblare. Planul nu a reuit, dar acest proiect a marcat o dezvoltare deosebit a interpretoarelor i compilatoarelor de Prolog, precum i o cretere mare a numrului de programatori n acest limbaj. Multe clase de probleme poate fi rezolvate n Prolog, existnd anumite categorii care sunt rezolvabile mult mai uor n Prolog dect n orice alt limbaj procedural. Astfel de probleme sunt n principal cele dedicate prelucrrii simbolice sau care necesit un proces de cutare a soluiei ntr-un spaiu posibil de transformri ale problemei. Prezentarea limbajului Prolog ce urmeaz este n principal orientat pe descrierea limbajului Prolog standard. Exemplele de programe din aceast parte ct i din partea a doua sunt rulate

utiliznd interpretorul ARITY Prolog pe microcalculatoare de tip IBM/PC, mediul de programare ARITY Prolog i particularitile lui sintactice fiind prezentate n partea a doua.

Entitile limbajului Prolog

Limbajul Prolog este un limbaj logic, descriptiv, care permite specificarea problemei de rezolvat n termenii unor fapte cunoscute despre obiectele universului problemei i a relaiilor existente ntre aceste obiecte. Execuia unui program Prolog const n deducerea implicaiilor acestor fapte i relaii, programul definind astfel o mulime de consecine ce reprezint nelesul sau semnificaia declarativ a programului.
Un program Prolog conine urmtoarele entiti: fapte despre obiecte i relaiile existente ntre aceste obiecte; reguli despre obiecte i relaiile dintre ele, care permit deducerea (inferarea) de noi

fapte pe baza celor cunoscute;


ntrebri, numite i scopuri, despre obiecte i relaiile dintre ele, la care programul

rspunde pe baza faptelor i regulilor existente.

1.1

Fapte

Faptele sunt predicate de ordinul nti de aritate n considerate adevrate. Ele stabilesc relaii ntre obiectele universului problemei. Numrul de argumente ale faptelor este dat de aritatea (numrul de argumente) corespunztoare a predicatelor. Exemple: Fapt: papagal(coco). iubete(mihai, maria). iubete(mihai, ana). frumoas(ana). bun(gelu). deplaseaz(cub, camera1, camera2). Aritate: 1 2 2 1 1 3

Interpretarea particular a predicatului i a argumentelor acestuia depinde de programator. Ordinea argumentelor, odat fixat, este important i trebuie pstrat la orice alt utilizare a faptului, cu aceeai semnificaie. Mulimea faptelor unui program Prolog formeaz baza de cunotine Prolog. Se va vedea mai trziu c n baza de cunotinte a unui program Prolog sunt incluse i regulile Prolog.

1.2

Scopuri

Obinerea consecinelor sau a rezultatului unui program Prolog se face prin fixarea unor scopuri care pot fi adevrate sau false, n funcie de coninutul bazei de cunotine Prolog. Scopurile sunt predicate pentru care se dorete aflarea valorii de adevr n contextul faptelor existente n baza de cunotine. Cum scopurile pot fi vzute ca ntrebri, rezultatul unui program Prolog este rspunsul la o ntrebare (sau la o conjuncie de ntrebri). Acest rspuns
7

poate fi afirmativ, yes, sau negativ, no. Se va vedea mai trziu c programul Prolog, n cazul unui rspuns afirmativ la o ntrebare, poate furniza i alte informaii din baza de cunotine. Exemplu
Considernd baza de cunotine specificat anterior, se pot pune diverse ntrebri, cum ar fi:

?- iubeste(mihai, maria). yes deoarece acest fapt exist n baza de cunotine ?- papagal(coco). yes ?- papagal(mihai). no deoarece acest fapt nu exist n baza de cunotine ?- inalt(gelu). no

1.3

Variabile

n exemplele prezentate pn acum, argumentele faptelor i ntrebrilor au fost obiecte particulare, numite i constante sau atomi simbolici. Predicatele Prolog, ca orice predicate n logica cu predicate de ordinul I, admit ca argumente i obiecte generice numite variabile. n Prolog, prin convenie, numele argumentelor variabile ncepe cu liter iar numele constantelor simbolice ncepe cu liter mic. O variabil poate fi instaniat (legat) dac exist un obiect asociat acestei variabile, sau neinstaniat (liber) dac nu se tie nc ce obiect va desemna variabila.
La fixarea unui scop Prolog care conine variabile, acestea sunt neinstaniate iar sistemul ncearc satisfacerea acestui scop cutnd printre faptele din baza de cunotine un fapt care poate identifica cu scopul, printr-o instaniere adecvat a variabilelor din scopul dat. Este vorba de fapt de un proces de unificare a predicatului scop cu unul din predicatele fapte existente n baza de cunotine. La ncercarea de satisfacere a scopului, cutarea se face ntotdeauna pornind de la nceputul bazei de cunotine. Dac se ntlnete un fapt cu un simbol predicativ identic cu cel al scopului, variabilele din scop se instaniaz conform algoritmului de unificare i valorile variabilelor astfel obinute sunt afiate ca rspuns la satisfacerea acestui scop.

Exemple: ?- papagal(CineEste). CineEste = coco ?- deplaseaza(Ce, DeUnde, Unde). Ce = cub, DeUnde = camera1, Unde = camera2 ?- deplaseaza(Ce, Aici, Aici). no
Cum se comport sistemul Prolog n cazul n care exist mai multe fapte n baza de cunotine care unific cu ntrebarea pus? n acest caz exist mai multe rspunsuri la ntrebare, corespunznd mai multor soluii ale scopului fixat. Prima soluie este dat de prima unificare i exist attea soluii cte unificri diferite exist. La realizarea primei unificri se marcheaz faptul care a unificat 8

i care reprezint prima soluie. La obinerea urmtoarei soluii, cutarea este reluat de la marcaj n jos n baza de cunotine. Obinerea primei soluii este de obicei numit satisfacerea scopului iar obinerea altor soluii, resatisfacerea scopului. La satisfacera unui scop cutarea se face ntotdeauna de la nceputul bazei de cunotine. La resatisfacerea unui scop, cutarea se face ncepnd de la marcajul stabilit de satisfacerea anterioar a acelui scop. Sistemul Prolog, fiind un sistem interactiv, permite utilizatorului obinerea fie a primului rspuns, fie a tuturor rspunsurilor. n cazul n care, dup afiarea tuturor rspunsurilor, un scop nu mai poate fi resatisfcut, sistemul rspunde no.

Exemple: ?- iubeste(mihai, X). X = maria; tastnd caracterul ; i Enter, cerem o nou soluie X = ana; no ?- iubeste(Cine, PeCine). Cine = mihai, PeCine = maria; Cine = mihai, PeCine = ana; no
Exist deci dou soluii pentru scopul iubeste(mihai, X) i tot dou soluii pentru scopul iubeste(Cine, PeCine), considernd tot baza de cunotine prezentat n seciunea 1.1.

1.4

Reguli
S :- S1, S2, Sn.

O regul Prolog exprim un fapt care depinde de alte fapte i este de forma:

cu semnificaia prezentat la nceputul acestui capitol. Fiecare Si, i = 1,n i S au forma faptelor Prolog, deci sunt predicate, cu argumente constante, variabile sau structuri. Faptul S care definete regula, se numete antet de regul, iar S1, S2, Sn formeaz corpul regulii i reprezint conjuncia de scopuri care trebuie satisfcute pentru ca antetul regulii s fie satisfcut.
Fie urmtoarea baz de cunotine Prolog:

frumoasa(ana). bun(vlad). cunoaste(vlad, maria). cunoaste(vlad, ana). iubeste(mihai, maria). iubeste(X, Y) :bun(X), cunoaste(X, Y), frumoasa(Y).

%1 %2 %3 %4 %5 %6

Se observ c enunul (6) definete o regul Prolog; relaia iubeste(Cine, PeCine), fiind definit att printr-un fapt (5) ct i printr-o regul (6).

n condiiile existenei regulilor n baza de cunotine Prolog, satisfacerea unui scop se face printr-un procedeu similar cu cel prezentat n Seciunea 1.2, dar unificarea scopului se ncearc att cu fapte din baza de cunotine, ct i cu antetul regulilor din baz. La unificarea unui scop cu antetul unei reguli, pentru a putea satisface acest scop trebuie satisfcut regula. Aceasta revine la a satisface toate faptele din corpul regulii, deci conjuncia de scopuri. Scopurile din corpul regulii devin subscopuri a cror satisfacere se va ncerca printr-un mecanism similar cu cel al satisfacerii scopului iniial. Pentru baza de cunotine descris mai sus, satisfacerea scopului

?- iubeste(vlad, ana). se va face n urmtorul mod. Scopul unific cu antetul regulii (6) i duce la instanierea variabilelor din regula (6): X = vlad i Y = ana. Pentru ca acest scop s fie ndeplinit, trebuie ndeplinit regula, deci fiecare subscop din corpul acesteia. Aceasta revine la ndeplinirea scopurilor bun(vlad), care reuete prin unificare cu faptul (2), cunoaste(vlad, ana), care reuete prin unificare cu faptul (4), i a scopului frumoasa(ana), care reuete prin unificare cu faptul (1). n consecin, regula a fost ndeplinit, deci i ntrebarea iniial este adevarat, iar sistemul rspunde yes.
Ce se ntmpl dac se pune ntrebarea:

?- iubeste(X, Y). Prima soluie a acestui scop este dat de unificarea cu faptul (5), iar rspunsul este: X = mihai, Y = maria
Sistemul Prolog va pune un marcaj n dreptul faptului (5) care a satisfcut scopul. Urmtoarea soluie a scopului iubeste(X, Y) se obine ncepnd cutarea de la acest marcaj n continuare n baza de cunotine. Scopul unific cu antetul regulii (6) i se vor fixa trei noi subscopuri de ndeplinit, bun(X), cunoaste(X, Y) i frumoasa(Y). Scopul bun(X) este satisfcut de faptul (2) i variabila X este instaniat cu valoarea vlad, X = vlad . Se ncearc acum satisfacerea scopului cunoaste(vlad, Y), care este satisfcut de faptul (3) i determin instanierea Y = maria. Se introduce n baza de cunotine un marcaj asociat scopului cunoaste(vlad, Y), care a fost satisfcut de faptul (3). Se ncearc apoi satisfacerea scopului frumoasa(maria). Acesta eueaz. n acest moment sistemul intr ntr-un proces de backtracking n care se ncearc resatisfacerea scopului anterior satisfcut, cunoaste(vlad, Y), n sperana c o noua soluie a acestui scop va putea satisface i scopul curent care a euat, frumoasa(Y). Resatisfacerea scopului cunoaste(vlad, Y) se face pornind cutarea de la marcajul asociat scopului n jos, deci de la faptul (3) n jos. O nou soluie (resatisfacere) a scopului cunoaste(vlad, Y) este dat de faptul (4) care determin instanierea Y = ana. n acest moment se ncearc satisfacerea scopului frumoasa(ana). Cum este vorba de un nou scop, cutarea se face de la nceputul bazei de cunotine i scopul frumoasa(ana) este satisfcut de faptul (1). n consecin a doua soluie a scopului iubeste(X, Y) este obinut i sistemul rspunde:

X = vlad, Y = ana urmnd un mecanism de backtracking, descris intuitiv n figura 1 prin prezentarea arborilor de deducie construii de sistemul Prolog.

10

iubeste(X,Y) %5 iubeste(mihai,maria) SUCCES solutie 1: X=mihai, Y=maria

iubeste(X,Y)

bun(X) %2 bun(vlad)

cunoaste(X,Y) cunoaste(vlad,Y)

%6 frumoasa(Y) frumoasa(maria) %1 frumoasa(ana) SUCCES

SUCCES %3 cunoaste(vlad,maria) %4 cunoaste(vlad,ana) INSUCCES SUCCES SUCCES

solutie 2: X=vlad, Y=ana

Figura 1. Mecanismul de satisfacere a scopurilor n Prolog


La ncercarea de resatisfacere a scopului iubeste(X, Y), printr-un mecanism similar, se observ c nu mai exist alte solutii. n concluzie, fiind dat baza de fapte i reguli Prolog anterioar, comportarea sistemului Prolog este:

?- iubeste(X, Y). X = mihai, Y = maria; X = vlad, Y = ana; no Observaii:


La satisfacerea unei conjuncii de scopuri n Prolog, se ncearc satisfacerea fiecrui

scop pe rnd, de la stnga la dreapta. Prima satisfacere a unui scop determin plasarea unui marcaj n baza de cunotine n dreptul faptului sau regulii care a determinat satisfacerea scopului.
Dac un scop nu poate fi satisfcut (eueaz), sistemul Prolog se ntoarce i ncearc

resatisfacerea scopului din stnga, pornind cutarea n baza de cunotine de la marcaj n jos. nainte de resatisfacerea unui scop se elimin toate instanierile de variabile determinate de ultima satisfacere a acestuia. Dac cel mai din stnga scop

11

din conjuncia de scopuri nu poate fi satisfcut, ntreaga conjuncie de scopuri eueaz.


Aceast comportare a sistemului Prolog n care se ncearc n mod repetat

satisfacerea i resatisfacerea scopurilor din conjunciile de scopuri se numete backtracking.


n Seciunea 4 se va discuta pe larg structura de control a sistemului Prolog,

mecanismul fundamental de backtracking i modurile n care se poate modifica parial acest mecanism.

1.5

Un program Prolog simplu

Simplitatea i expresivitatea limbajului Prolog poate fi pus n evident de urmtorul exemplu de descriere a unui circuit logic "poart I". Se consider poarta I ca fiind construit prin conectarea unei "pori I-NU" cu un inversor. ntregul circuit este definit de relaia: poarta_si(Intrare1, Intrare2, Iesire) pe baza relaiilor poarta_si_nu(Intrare1, Intrare2, Iesire) inversor(Intrare, Iesire).
n figura 2 este prezentat schema porii I n care se observ c inversorul este construit dintr-un tranzistor cu sursa conectat la mas i un rezistor cu un capt conectat la alimentare. Poarta tranzistorului este intrarea inversorului, n timp ce cellalt capt al rezistorului trebuie conectat la colectorul tranzistorului care formeaz ieirea inversorului.

n1 n2 n4 n3

n5

Figura 2. Un circuit logic poart I


Variabilele comune ntre predicate sunt utilizate pentru a specifica legturile comune.

rezistor(alimentare, n1). rezistor(alimentare, n2).


12

tranzistor(n2, masa, n1). tranzistor(n2, masa, n1). tranzistor(n3, n4, n2). tranzistor(n5, masa, n4). inversor(Intrare, Iesire) :tranzistor(Intrare, masa, Iesire), rezistor(alimentare, Iesire). poarta_si_nu(Intrare1, Intrare2, Iesire) :tranzistor(Intrare1, X, Iesire), tranzistor(Intrare2, masa, X), rezistor(alimentare, Iesire). poarta_si(Intrare1, Intrare2, Iesire) :poarta_si_nu(Intrare1, Intrare2, X), inversor(X, Iesire).
Pentru ntrebarea

?- poarta_si(In1, In2, Iesire). In1 = n3, In2= n5, Iesire = n1 rspunsul sistemului Prolog confirm faptul c circuitul descris este o poart I, identificnd intrrile i ieirile corespunztoare.

Sintaxa limbajului Prolog

Aa cum s-a artat n seciunea anterioar, un program Prolog este format din fapte, reguli i ntrebri, acestea fiind construite pe baza predicatelor definite de utilizator sau predefinite. n orice sistem Prolog exist o mulime de predicate predefinite, unele dintre acestea fiind predicate standard, iar altele depinznd de implementare. Argumentele predicatelor Prolog, prin analogie cu logica predicatelor de ordinul I, se numesc termeni, i pot fi constante, variabile sau structuri.

2.1

Constante

Constantele definesc obiecte specifice, particulare, sau relaii particulare. Exist dou tipuri de constante: atomi i numere. Atomii sunt constante simbolice care ncep, de obicei, cu o liter i pot conine litere, cifre i caracterul _. Exist i alte caractere ce pot forma atomi speciali, care au o semnificaie aparte n limbaj. Atomii pot desemna:
obiecte constante care sunt argumentele predicatelor, de exemplu atomii mihai i

maria n faptul iubeste(mihai, maria);


predicate Prolog, fie cele definite de utilizator, fie cele predefinite n sistem; de

exemplu atomul iubeste n faptul iubeste(mihai, maria);


13

atomi speciali, de exemplu atomii :- i ?- ; diverse alte reguli de construcie sintactic a atomilor depind de implementare. Numerele pot fi ntregi sau reale; sintaxa particular acceptat ct i domeniile de definiie depinznd de implementare. Detalii despre formatul numerelor i a altor tipuri de constante din mediul ARITY Prolog sunt descrise n partea a doua.

2.2

Variabile

Variabilele sunt, din punct de vedere sintactic, tot atomi, dar ele au o semnificaie special, aa cum s-a artat n Seciunea 1.3. Spre deosebire de regulile generale admise pentru construcia atomilor, numele unei variabile poate ncepe i cu simbolul _, ceea ce indic o variabil anonim. Utilizarea unei variabile anonime semnific faptul c nu intereseaz valoarea la care se va instania acea variabil.
De exemplu, interogarea ?- iubeste( _, maria). semnific faptul c se ntreab dac exist cineva care o iubete pe Maria, dar nu intereseaz cine anume. Limbajul Prolog face distincia ntre litere mari i litere mici (este case sensitive). Se reamintete c, din punctul de vedere al conveniei Prolog, numele oricrei variabile trebuie s nceap fie cu liter mare, fie cu _.

2.3

Structuri

O structur Prolog este un obiect ce desemneaza o colecie de obiecte corelate logic, care formeaz componentele structurii. Un exemplu este structura asociat obiectului carte, care este format din componentele: titlu carte, autor, i an apariie. Un fapt ce refer relaia de posedare a unei cri de Prolog de ctre Mihai poate fi exprimat astfel: poseda(mihai, carte(prolog, clocksin, 1981)). unde carte(prolog, clocksin, 1981) este o structur cu numele carte i cu trei componente: prolog, clocksin i 1981. Se admit i structuri imbricate, de exemplu: poseda(mihai, carte(prolog, autori(clocksin, mellish), 1981)). unde predicatul poseda are dou argumente: primul argument este constanta mihai, iar cel de-al doilea este structura carte(prolog ....), cu dou componente, a doua component fiind structura autori(clocksin, mellish).
n Prolog, o structur se definete prin specificarea:

(1) (2)

numelui structurii ( functorul structurii); elementelor structurii (componentele structurii).

Structurile Prolog pot fi utilizate pentru reprezentarea structurilor de date, de exemplu liste sau arbori. n Seciunea 2.6 se vor prezenta structuri Prolog predefinite care permit lucrul cu liste. Un arbore binar poate fi reprezentat n Prolog tot printr-o structur, de exemplu:

arb(barbu, arb(ada, vid, vid), vid). reprezint un arbore binar cu cheia din rdcina barbu, cu subarborele drept vid i cu subarborele stng format dintr-un singur nod cu cheia ada. Observaii:
14

Sintaxa structurilor este aceeai cu cea a faptelor Prolog. Un predicat Prolog poate fi

vzut ca o structur a crui functor este numele predicatului, iar argumentele acestuia reprezint componentele structurii.
Considerarea predicatelor Prolog ca structuri prezint un interes deosebit; att datele

ct i programele n Prolog au aceeai form, ceea ce faciliteaz tratarea uniform i sinteza dinamic de programe. n Seciunea 4.3 se vor prezenta predicate predefinite n Prolog care permit sinteza i execuia dinamic a programelor Prolog.

2.4

Operatori

Uneori este convenabil s se scrie anumii functori (nume de structuri sau predicate) n form infixat. Aceasta este o form sintactic ce mrete claritatea programului, cum ar fi cazul operatorilor aritmetici sau al operatorilor relaionali.
Limbajul Prolog ofer o mulime de operatori, unii care se regsesc n aproape orice implementare, iar alii care sunt specifici unei versiuni particulare de implementare a limbajului. n continuare se vor prezenta o parte dintre operatorii din prima categorie.

(1) Operatori aritmetici Operatorii aritmetici binari, cum ar fi +, -, *, /, pot fi scrii n Prolog n notaie infixat; de exemplu: 1 + 2*(X * Y) / Z Aceasta sintaxa este de fapt o rescriere infixat a formei prefixate a structurilor echivalente: +(1, 2) / (*(X, Y), Z)
Este important de reinut c operatorii aritmetici sunt o rescriere infixat a unor structuri deoarce valoarea expresiei astfel definit nu este calculat. Evaluarea expresiei se face la cerere n cazul n care se foloseste operatorul predefinit infixat is, de exemplu:

X is 1 + 2. va avea ca efect instanierea variabilei X la valoarea 3. (2) Operatori relaionali Operatorii relaionali sunt predicate predefinite infixate. Un astfel de operator este operatorul de egalitate =. Predicatul (operatorul) de egalitate funcioneaz ca i cum ar fi definit prin urmtorul fapt: X = X. iar ncercarea de a satisface un scop de tipul X = Y se face prin ncercarea de a unifica X cu Y. Din aceasta cauz, dndu-se un scop de tipul X = Y, regulile de decizie care indic dac scopul se ndeplinete sau nu sunt urmtoarele:

15

Dac X este variabil neinstaniat, iar Y este instaniat la orice obiect Prolog,

atunci scopul reuete. Ca efect lateral, X se va instania la aceeai valoare cu cea a lui Y. De exemplu: ?- carte(barbu, poezii) = X. este un scop care reuete i X se instaniaz la carte(barbu, poezii).
Dac att X ct i Y sunt variabile neinstaniate, scopul X = Y reuete, variabila X

este legat la Y i reciproc. Aceasta nseamn c ori de cte ori una dintre cele dou variabile se instaniaz la o anumit valoare, cealalt variabila se va instania la aceeai valoare.
Atomii i numerele sunt ntotdeauna egali cu ei nii. De exemplu, urmtoarele

scopuri au rezultatul marcat drept comentariu: mihai = mihai mare = mic 102 = 102 1010 = 1011 % este satisfcut % eueaz % reuete % eueaz

Dou structuri sunt egale dac au acelai functor, acelai numr de componente i

fiecare component dintr-o structur este egal cu componenta corespunztoare din cealalt structur. De exemplu, scopul: autor(barbilian, ciclu(uvedenrode, poezie(riga_crypto))) = autor(X, ciclu(uvedenrode, poezie(Y))) este satisfcut, iar ca efect lateral se fac instanierile: X = barbilian i Y = riga_crypto.
Operatorul de inegalitate \= se definete ca un predicat opus celui de egalitate. Scopul X \= Y reuete dac scopul X = Y nu este satisfcut i eueaz dac X = Y reuete. n plus, exist predicatele relaionale de inegalitate definite prin operatorii infixai >, <, =<, >=, cu semnificaii evidente. Un operator interesant este =:=. El face numai evaluare aritmetic i nici o instaniere. Exemplu:

?- 1 + 2 =:= 2 + 1. yes ?- 1 + 2 = 2 + 1. no
Predicatul = = testeaz echivalena a dou variabile. El consider cele dou variabile egale doar dac ele sunt deja partajate. X = = Y reuete ori de cte ori X = Y reuete, dar reciproca este fals:

?- X = = X. X=_23 ?- X= =Y.

%variabil neinstaniat

16

no ?- X=Y, X= =Y. X=_23, Y=_23

% X i Z variabile neinstaniate partajate

n cele mai multe implementri Prolog exist predefinit operatorul de obinere a modulului unui numr, mod; scopul

X is 5 mod 3. reuete i X este instaniat la 2.


Comentariile dintr-un program Prolog sunt precedate de caracterul %.

Exemple: 1. Presupunnd c nu exist operatorul predefinit mod, se poate scrie un predicat Prolog cu efect similar. O definiie posibil a predicatului modulo(X, Y, Z), cu semnificaia argumentelor Z = X mod Y , presupunnd X, Y > 0, este: % modulo(X, Y, Z) modulo(X, Y, X) :- X < Y. modulo(X, Y, Z) :- X >= Y, X1 is X - Y, modulo(X1, Y, Z). 2. Plecnd de la predicatul modulo definit anterior, se poate defini predicatul de calcul al celui mai mare divizor comun al dou numere, conform algoritmului lui Euclid, presupunnd X > 0 , Y > 0 , astfel: % cmmdc(X, Y, C) cmmdc(X, 0, X). cmmdc(X, Y, C) :- modulo(X, Y, Z), cmmdc(Y, Z, C). La ntrebarea ?- cmmdc(15, 25, C). C=5 rspunsul sistemului este corect. n cazul n care se ncearc obinerea unor noi soluii (pentru semnificaia cmmdc acest lucru este irelevant, dar intereseaz din punctul de vedere al funcionrii sistemului Prolog) se observ ca sistemul intr ntr-o bucl infinit datorita imposibilitii resatisfacerii scopului modulo(X, Y, Z) pentru Y = 0. Dac la definiia predicatului modulo se adaug faptul: modulo(X, 0, X). atunci predicatul modulo(X, Y, Z) va genera la fiecare resatisfacere aceeai soluie, respectiv solutia corect, la infinit. Cititorul este sftuit s traseze execuia predicatului cmmdc n ambele variante de implementare a predicatului modulo.

17

3. Calculul ridicrii unui numr natural la o putere natural se poate face definind urmtorul predicat: % expo(N, X, XlaN) expo( _ , 0, 0). expo(0, _ , 1). expo(N, X, Exp) :- N > 0, N1 is N - 1, expo(N1, X, Z), Exp is Z * X.

2.5

Operatori definii de utilizator

Limbajul Prolog permite definirea de noi operatori de ctre programator prin introducerea n program a unor clauze de form special, numite directive. Directivele acioneaz ca o definire de noi operatori ai limbajului n care se specific numele, precedena i tipul (infixat, prefixat sau postfixat) operatorului. Se reamintete faptul c orice operator Prolog este de fapt o structur care are ca functor numele operatorului i ca argumente argumentele operatorului, dar este scris ntr-o sintax convenabil. La definirea de noi operatori n Prolog, se creeaz de fapt noi structuri crora le este permis o sintax special, conform definiiei corespunztoare a operatorului. Din acest motiv, nu se asociaz nici o operaie operatorilor definii de programator. Operatorii noi definii sunt utilizai ca functori numai pentru a combina obiecte n structuri i nu pentru a executa aciuni asupra datelor, dei denumirea de operator ar putea sugera o astfel de aciune.
De exemplu, n loc de a utiliza structura:

are(coco, pene) se poate defini un nou operator are :- op(600, xfx, are). caz n care este legal expresia coco are pene.
Definirea de noi operatori se face cu ajutorul directivei:

:- op(preceden-operator, tip-operator, nume-operator).


Operatorii sunt atomi iar precedenta lor trebuie s fie o valoare ntreag ntr-un anumit interval i corelat cu precedena operatorilor predefinii n limbaj. Tipul operatorilor fixeaz caracterul infixat, prefixat sau postfixat al operatorului i precedena operanzilor si. Tipul operatorului se definete utiliznd una din urmtoarele forme standard:

(1) (2) (3)

operatori infixai: operatori prefixai: operatori postfixai:

xfx xfy yfx fx xf fy yf

18

unde f reprezint operatorul, iar x i y operanzii si. Utilizarea simbolului x sau a simbolului y depinde de precedena operandului fa de operator. Precedena operanzilor se definete astfel:
un argument ntre paranteze sau un argument nestructurat are precedena 0; un argument de tip structur are precedena egal cu cea a functorului operator.

Observaie: n ARITY Prolog, cu ct valoare-preceden-operator este mai mare, cu att operatorul are o preceden mai mic i invers.
Semnificaiile lui x i y n stabilirea tipului operatorului sunt urmtoarele: x reprezint un argument (operand) cu preceden strict mai mic dect cea a

functorului (operatorului) f precedena( x ) < precedena( f )


y reprezint un argument (operand) cu preceden mai mic sau egal cu cea a

functorului (operandului) f precedena( y ) precedena( f )


Aceste reguli ajut la eliminarea ambiguitii operatorilor multipli cu aceeai preceden. De exemplu, operatorul predefinit n Prolog - (minus) este definit din punct de vedere al tipului ca yfx, ceea ce nseamn c structura a - b - c este interpretat ca (a - b) - c i nu ca a - (b - c) . Dac acest operator ar fi fost definit ca xfy, atunci interpretarea structurii a - b - c ar fi fost a - (b - c) . Numele operatorului poate fi orice atom Prolog care nu este deja definit n Prolog. Se poate folosi i o list de atomi, dac se definesc mai muli operatori cu aceeai preceden i acelai tip.

Exemple: :- op(100, xfx, [este, are]). :- op(100, xf, zboara). coco are pene. coco zboara. coco este papagal. bozo este pinguin. ?- Cine are pene. Cine = coco ?- Cine zboara. Cine = coco ?- Cine este Ce. Cine = coco, Ce = papagal; Cine = bozo, Ce = pinguin; no
n condiiile n care se adaug la baza de cunotine anterioar i definiia operatorilor daca, atunci i si

:- op(870, fx, daca).


19

:- op(880, xfx, atunci). :- op(880, xfy, si). urmtoarea structur este valid n Prolog: daca Animalul are pene i Animalul zboara atunci Animalul este pasare.

2.6

Liste

O list este o structur de date ce reprezint o secven ordonat de zero sau mai multe elemente. O list poate fi definit recursiv astfel: (1) (2) lista vid (lista cu 0 elemente) este o list o list este o structur cu dou componente: primul element din list (capul listei) i restul listei (lista format din urmatoarele elemente din lista).

Sfritul unei liste este de obicei reprezentat ca lista vid.


n Prolog structura de list este reprezentat printr-o structur standard, predefinit, al crei functor este caracterul . i are dou componente: primul element al listei i restul listei. Lista vid este reprezentat prin atomul special [ ]. De exemplu, o list cu un singur element a se reprezint n Prolog, prin notaie prefixat astfel:

.(a, [ ])

avnd sintaxa in ARITY Prolog

'.'(a, [ ])

iar o list cu trei elemene, a, b, c, se reprezint ca: .(a, . (b, . (c, [ ]))) cu sintaxa n ARITY Prolog '.'(a, '. '(b, '. '(c, [ ]))).
Deoarece structura de list este foarte des utilizat n Prolog, limbajul ofer o sintax alternativ pentru descrierea listelor, format din elementele listei separate de virgul i ncadrate de paranteze drepte. De exemplu, cele dou liste anterioare pot fi exprimate astfel:

[a] [a, b, c]
Aceast sintax a listelor este general i valabil n orice implementare Prolog. O operaie frecvent asupra listelor este obinerea primului element dintr-o list i a restului listei, deci a celor dou componente ale structurii de list. Aceasta operaie este realizat n Prolog de operatorul de scindare a listelor | scris sub urmtoarea form:

[Prim | Rest]
Variabila Prim st pe postul primului element din list, iar variabila Rest pe postul listei care conine toate elementele din list cu excepia primului. Acest operator poate fi aplicat pe orice list care conine cel puin un element. Dac lista conine exact un element, Rest va reprezenta lista vid. ncercarea de identificare a structurii [Prim | Rest] cu o list vid duce la eec. Mergnd mai departe, se pot obine chiar primele elemente ale listei i restul listei. Iat cteva echivalene:

[a, b, c] = [a | [b, c] ] = [a, b | [c] ] = [a, b, c | [ ] ] = [a | [b | [c] ] ] = [a | [b | [c | [ ] ] ] ].


20

n Prolog elementele unei liste pot fi atomi, numere, liste i n general orice structuri. n consecin se pot construi liste de liste.

Exemple: 1. Se poate defini urmtoarea structur de list: [carte(barbu, poezii), carte(clocksin, prolog)] 2. Considernd urmtoarele fapte existente n baza de cunotine Prolog pred([1, 2, 3, 4]). pred([coco, sta, pe, [masa, alba]]). se pot pune urmtoarele ntrebri obinnd rspunsurile specificate: ?- pred([Prim | Rest]). Prim = 1, Rest = [2, 3, 4]; Prim = coco, Rest = [sta, pe, [masa, alba]]; no ?- pred([ _, _ , _ , [ _ | Rest]]) Rest = [alba] 3. Un predicat util n multe aplicaii este cel care testeaz apartenena unui element la o list i care se definete astfel: % membru(Element, Lista) membru(Element, [Element | _ ]). membru(Element, [ _ | RestulListei]) :- membru(Element, RestListei). Funcionarea acestui scurt program Prolog poate fi urmrit cernd rspunsul sistemului la urmtoarele scopuri: ?- membru(b, [a, b, c]). yes ?- membru(X, [a, b, c]). X = a; X = b; X = c; no ?- membru(b, [a, X, b]). X = b; X = _0038; no %1 %2

%3

21

Deci pentru cazul n care primul argument este o variabil (%2) exist trei soluii posibile ale scopului membru(Element, Lista). Dac lista conine o variabil (%3) exist dou soluii pentru forma listei ([a, b, b] sau [a, _ , b]). 4. Un alt predicat util este cel de concatenare a dou liste L1 i L2, rezultatul concatenrii obinndu-se n lista L3, iniial neinstaniat. % conc(L1, L2, L3) conc([], L2, L2). % lista vid concatenat cu L2 este L2. conc([Prim1|Rest1], Lista2, [Prim1|Rest3]) :conc(Rest1, Lista2, Rest3). % lista rezultat este primul element % al sublistei curente din L1 concatenat % cu lista ntoars de apelul recursiv. ?- conc([a, b], [c, d, e], L3). L3 = [a, b, c, d, e]; no ?- conc([a, b], [c, d, e], L3). L3 = [a, b, c, d, e]. % . i Enter cnd nu cutm alte soluii yes ?- conc(L1, [c, d, e], [a, b, c, d, e]). L1 = [a, b]; no ?- conc([a, b], L2, [a, b, c, d, e]). L2 = [c, d, e]; no ?- conc(L1, L2, [a, b]). L1 = [ ], L2 = [a, b]; L1 = [a], L2 = [b]; L1 = [a, b], L2 = [ ]; no Se observ c pentru cazul n care predicatul de concatenare are un singur argument neinstaniat exist o singur soluie, iar pentru cazul n care primele dou argumente sunt neinstaniate (variabile) se obin mai multe soluii, corespunztoare tuturor variantelor de liste care prin concatenare genereaz cea de a treia list. 5. Urmtorul exemplu definete predicatul de testare a existenei unei sortri n ordine cresctoare a elementelor unei liste de ntregi. Predicatul reuete dac elementele listei sunt sortate cresctor i eueaz n caz contrar. % sortcresc(Lista) sortcresc([ ]). sortcresc([ _ ]). % lista vid se consider sortat % lista cu un singur element este sortat
22

sortcresc([X, Y | Rest]) :- X = < Y, sortcresc([Y | Rest]). ?- sortcresc([1, 2, 3, 4]). yes ?- sortcresc([1, 3, 5, 4]). no ?- sortcresc([ ]). yes Dac se consider c lista vid nu este o list sortat cresctor atunci se poate elimina faptul: sortcresc([ ]). din definiia predicatului sortcresc, comportarea lui pentru liste diferite de lista vid rmnnd aceeai. Observaii:
Exemplul 3 pune n eviden o facilitate deosebit de interesant a limbajului Prolog,

respectiv puterea generativ a limbajului. Predicatul membru poate fi utilizat att pentru testarea apartenenei unui element la o list, ct i pentru generarea, pe rnd, a elementelor unei liste prin resatisfacere succesiv. n anumite contexte de utilizare aceast facilitate poate fi folositoare, iar n altele ea poate genera efecte nedorite atunci cnd predicatul membru este utilizat n definirea altor predicate, aa cum se va arta n partea a doua a lucrrii.
Aceeai capacitate generativ a limbajului Prolog poate fi observat i n Exemplul

4 unde n funcie de combinaiile de argumente instaniate i neinstaniate, predicatul conc poate produce rezultate relativ diferite.
La definirea unui predicat p care va fi utilizat n definirea altor predicate trebuie

ntotdeauna s se analizeze numrul de soluii i soluiile posibile ale predicatului p. Acest lucru este necesar deoarece dac p apare ntr-o conjuncie de scopuri p1,, pi-1, p ,pi+1,, pn i unul dintre scopurile pi+1,, pn eueaz, mecanismul de backtracking din Prolog va ncerca resatisfacerea scopului p. Numrul de soluii i soluiile scopului p influeneaz astfel, n mod evident, numrul de soluii i soluiile conjunciei de scopuri p1,, pi-1, p ,pi+1,, pn. Exemple n acest sens i modaliti de reducere a numrului total de soluii ale unui corp vor fi prezentate n Seciunea 4.2.

Limbajul Prolog i logica cu predicate de ordinul I

Limbajul Prolog este un limbaj de programare logic. Dei conceput iniial pentru dezvoltarea unui interpretor de limbaj natural, limbajul s-a impus ca o soluie practic de
23

construire a unui demonstrator automat de teoreme folosind rezoluia. Demonstrarea teoremelor prin metoda rezoluiei necesit ca axiomele i teorema s fie exprimate n forma clauzal, adic o disjuncie de literali, unde un literal este un predicat sau un predicat negat. Pentru detalii despre noiunea de clauz i modul de transformare a unei formule din logica cu predicate de ordinul I n form clauzal se poate consulta [Flo93]. Sintaxa i semantica limbajului Prolog permit utilizarea numai a unei anumite forme clauzale a formulelor bine formate: clauze Horn distincte.
Deoarece faptele i regulile Prolog sunt n form clauzal, forma particular a clauzelor fiind clauze Horn distincte, ele se mai numesc i clauze Prolog.

Definiie. Se numete clauz Horn o clauz care conine cel mult un literal pozitiv. O clauz Horn poate avea una din urmtoarele patru forme: (1) (2) (3) (4) o clauz unitar pozitiv format dintr-un singur literal pozitiv (literal nenegat); o clauz negativ format numai din literali negai; o clauz format dintr-un literal pozitiv i cel puin un literal negativ, numit i clauz Horn mixt; clauz vid ( ).

Definiie. Se numete clauz Horn distinct o clauz care are exact un literal pozitiv, ea fiind fie o clauz unitar pozitiv, fie o clauz Horn mixt.
Clauzele Horn unitare pozitive se reprezint n Prolog prin fapte, iar clauzele Horn mixte prin reguli. O clauz Horn mixt de forma:

S1 S2 Sn S se exprim n Prolog prin regula: S :- S1, S2, Sn.


Semnificaia intuitiv a unei reguli Prolog are un corespondent clar n logica cu predicate de ordinul I dac se ine cont de faptul c o clauz Horn mixt poate proveni din urmtoarea formul bine format:

S1 S2 Sn S
Variabilele din clauzele distincte se transform n variabile Prolog, constantele din aceste formule n constante Prolog, iar funciile pot fi asimilate cu structuri Prolog. Deci argumentele unui predicat Prolog au forma termenilor din calculul cu predicate de ordinul I.

Exemple: 1. Fie urmtoarele enunuri: Orice sportiv este puternic. Oricine este inteligent i puternic va reui n via. Oricine este puternic va reui n via sau va ajunge btu. Exist un sportiv inteligent. Gelu este sportiv. Exprimnd enunurile n logica cu predicate de ordinul I se obin urmtoarele formule bine formate: A1. (x) (sportiv(x) puternic(x)) A2. (x) (inteligent(x) puternic(x) reuete(x))
24

A3. (x) (puternic(x) (reuete(x) btu(x))) A4. (x) (sportiv(x) inteligent(x)) A5. Sportiv(gelu) Axiomele se transform n forma clauzal i se obin urmtoarele clauze: C1. sportiv(x) puternic(x) C2. inteligent(x) ~ puternic(x) reuseste(x) C3. ~ puternic(x) reuseste(x) bataus(x) C4. sportiv(a) C4'. inteligent(a) C5. sportiv(gelu) Clauzele C1, C2, C4, C4' i C5 pot fi transformate n Prolog deoarece sunt clauze Horn distincte, dar clauza C3 nu poate fi transformat n Prolog. Programul Prolog care se obine prin transformarea acestor clauze este urmtorul: puternic(X) :- sportiv(X). reuseste(X) :- inteligent(X), puternic(X). sportiv(a). inteligent(a). sportiv(gelu). 2. Fie urmtoarele enunuri: (a) (b) (c) Orice numr raional este un numr real. Exist un numr prim. Pentru fiecare numr x exist un numr y astfel nct x < y .

Dac se noteaz cu prim(x) x este numr prim, cu rational(x) x este numr raional, cu real(x) x este numr real i cu mai_mic(x, y) x este mai mic dect y, reprezentarea sub form de formule bine formate n calculul cu predicate de ordinul I este urmtoarea: A1. (x) (raional(x) real(x)) A2. (x) prim(x) A3. (x) (y) mai_mic(x, y) Reprezentarea n form clauzal este: C1. ~ rational(x) real(x) C2. prim(a)
25

C3. mai_mic(x, mai_mare(x)) unde mai_mare(x) este funcia Skolem care nlocuiete variabila y cuantificat existenial. Forma Prolog echivalent a acestor clauze este: real(X) :- rational(X). prim(a). mai_mic(X, mai_mare(X)). unde mai_mare(x) este o structur Prolog.
Este evident c nu orice axiom poate fi transformat n Prolog i c, dintr-un anumit punct de vedere, puterea expresiv a limbajului este inferioar celei a logicii cu predicate de ordinul I. Pe de alt parte, limbajul Prolog ofer o mulime de predicate de ordinul II, adic predicate care accept ca argumente alte predicate Prolog, care nu sunt permise n logica cu predicate de ordinul I. Acest lucru ofer limbajului Prolog o putere de calcul superioar celei din logica clasic. Uneori, aceste predicate de ordinul II existente n Prolog pot fi folosite pentru a modela versiuni de programe Prolog echivalente cu o mulime de axiome care nu au o reprezentare n clauze Horn distincte. Se propune cititorului, dup parcurgerea seciunii urmtoare, s revin la Exemplul 1 i s ncerce gsirea unei forme Prolog relativ echivalente (cu un efect similar) cu clauza C3. Limbajul Prolog demonstreaz scopuri (teoreme) prin metoda respingerii rezolutive [Flo93]. Strategia rezolutiv utilizat este strategia de intrare liniar, strategie care nu este n general complet dar este complet pentru clauze Horn. Aceast strategie este deosebit de eficient din punct de vedere al implementrii i jutific astfel forma restricionat a clauzelor Prolog.

Structura de control a limbajului Prolog

n aceast seciune se prezint n detaliu structura de control specific sistemului Prolog. Spre deosebire de limbajele de programare clasice, n care programul definete integral structura de control i fluxul de prelucrri de date, n Prolog exist un mecanism de control predefinit.

4.1

Semnificaia declarativ i procedural a programelor Prolog

Semnificaia declarativ a unui program Prolog se refer la interpretarea strict logic a clauzelor acelui program, rezultatul programului fiind reprezentat de toate consecinele logice ale acestuia. Semnificaia declarativ determin dac un scop este adevrat (poate fi satisfcut) i, n acest caz, pentru ce instane de variabile este adevrat scopul. Se reamintete c o instan a unei clauze este clauza de baz (clauza fr variabile) obinut prin instanierea variabilelor din clauza iniial. n aceste condiii semnificaia declarativ a unui program Prolog se poate defini precum urmeaz: Definiie. Un scop S este adevrat ntr-un program Prolog, adic poate fi satisfcut sau deriv logic din program, dac i numai dac: 1. 2. exist o clauz C a programului; exist o instan I a clauzei C astfel nct: 2.1. antetul lui I s fie identic cu cel al lui S;
26

2.2. Observaii:

toate scopurile din corpul lui I sunt adevrate, deci pot fi satisfcute.

n definiia de mai sus clauzele refer att fapte ct i reguli Prolog. Antetul unei

clauze este antetul regulii dac clauza este o regul Prolog (clauz Horn mixt) i este chiar faptul dac clauza este un fapt Prolog (clauz unitar pozitiv). Corpul unui fapt este considerat vid i un fapt este un scop care se ndeplinete ntotdeauna.
n cazul n care ntrebarea pus sistemului Prolog este o conjuncie de scopuri,

definiia anterioar se aplic fiecrui scop din conjuncie.


Semnificaia procedural a unui program Prolog se refer la modul n care sistemul ncearc satisfacerea scopurilor, deci la strategia de control utilizat. Diferena dintre semnificaia declarativ i semnificaia procedural este aceea c cea de a doua definete, pe lnga relaiile logice specificate de program, i ordinea de satisfacere a scopurilor i subscopurilor. n prima seciune a acestui capitol s-a fcut o prezentare informal a modalitii procedurale de satisfacere a scopurilor n Prolog. n continuare se rafineaz aceast comportare. Din punct de vedere procedural, un program Prolog poate fi descris de schema bloc prezentat n figura 3. program Prolog

conjuncii de scopuri

Execuie sistem Prolog

indicator SUCCES/INSUCCES instanele variabilelor din scopuri

Figura 3. Comportarea procedural a sistemului Prolog


Semnificaia procedural a unui program Prolog poate fi descris de urmtorul algoritm n care L = {S1, S2, , Sn} este lista de scopuri de satisfcut, iar B este lista de instanieri (unificri) ale variabilelor din scopuri, iniial vid. Aceast list se va actualiza la fiecare apel.

Algoritm. Strategia de control Prolog SATISFACE(L,B) 1. dac L = {} % lista vid atunci afieaz B i ntoarce SUCCES. 2. Fie S1 primul scop din L i p predicatul referit de S1. Parcurge clauzele programului, de la prima clauz sau de la ultimul marcaj fixat, asociat lui p, pn ce se gsete o clauz C al crei antet unific cu S1. 3. dac nu exist o astfel de clauz atunci ntoarce INSUCCES. 4. Fie C de forma H :- D1,,Dm, m0. Plaseaz un marcaj n dreptul clauzei C, asociat lui p. (H conine predicatul p). 5. Redenumete variabilele din C i obtine C' astfel nct s nu existe nici o variabil comun ntre C' i L; C' este de tot forma H :- D1,,Dm. cu redenumirile fcute. 6. L { D1,,Dm, S2,,Sn } % dac C este fapt, atunci L se va reduce
27

7. Fie B1 instanierea variabilelor care rezult din unificarea lui S1 cu H. 8. Substituie variabilele din L cu valorile date de B1 i obine: L { D1,,Dm, S2,,Sn }. 9. B B B1. 10. dac SATISFACE(L, B)=SUCCES atunci afieaz B i ntoarce SUCCES. 11. repet de la 1. sfrit Observaii:
Algoritmul de mai sus reprezint de fapt implementarea strategiei rezolutive de

intrare liniar utilizat de limbajul Prolog, pentru care se impune o ordine prestabilit de considerare a clauzelor.
Algoritmul arat funcionarea sistemului pentru gsirea primei soluii. Indicatorul SUCCES/INSUCCES corespunde rspunsurilor de yes, respectiv no,

date de sistemul Prolog la ncercarea de satisfacere a unei liste de scopuri.


Urmtoarele dou exemple pun n eviden diferena dintre semnificaia declarativ i semnificaia procedural a programelor Prolog. Primul exemplu este un scurt program care definete relaiile de printe i strmo existente ntre membrii unei familii. Se dau patru definiii posibile ale relaiei de strmo, str1, str2, str3 i str4, toate fiind perfect corecte din punct de vedere logic, deci din punct de vedere a semnificaiei declarative a limbajului.

% parinte(IndividX, IndividY) % stramos(IndividX, IndividZ) parinte(vali, gelu). parinte(ada, gelu). parinte(ada, mia). parinte(gelu, lina). parinte(gelu, misu). parinte(misu, roco). str1(X, Z) :- parinte(X, Z). str1(X, Z) :- parinte(X, Y), str1(Y, Z). % Se schimb ordinea regulilor: str2(X, Z) :- parinte(X, Y), str2(Y, Z). str2(X, Z) :- parinte(X, Z). % Se schimb ordinea scopurilor n prima variant: str3(X, Z) :- parinte(X, Z).
28

str3(X, Z) :- str3(X, Y), parinte(Y, Z). % Se schimb att ordinea regulilor, ct i ordinea scopurilor: str4(X, Z) :- str4(X, Y), parinte(Y, Z). str4(X, Z) :- parinte(X,Z).

vali

ada

gelu

mia

lina

miu

roco
Figura 4. Arborele genealogic definit de programul Prolog
Figura 4 prezint arborele genealogic definit de faptele Prolog anterioare. n raport cu semantica declarativ a limbajului se pot schimba, fr a modifica nelesul logic, att ordinea clauzelor care definesc relaia de strmo, ct i ordinea scopurilor n corpul regulii de aflare a strmoilor. Schimbnd ordinea clauzelor se obine din predicatul str1 definiia alternativ a relaiei de strmo str2; schimbnd ordinea scopurilor din corpul regulii n varianta iniial se obine definiia predicatului str3; i, schimbnd att ordinea clauzelor, ct i ordinea scopurilor n regul, se obine predicatul str4. Comportarea programului folosind cele patru definiii alternative, dac se dorete aflarea adevrului relaiei de strmo pentru perechile (ada, miu) i (mia, roco), este cea prezentat n continuare.

?- str1(ada, misu). yes


Pentru acest scop, arborele de deducie este prezentat n figura 5.

str1(ada, misu) parinte(ada, misu) INSUCCES parinte(ada, Y), str1(Y, misu) Y=gelu parinte(ada, gelu) parinte(gelu, misu) SUCCES SUCCES
Figura 5. Arborele de deducie a scopului str1(ada, misu) ?- str2(ada, misu).
29

str1(gelu, misu)

yes ?- str3(ada, misu). yes


Pentru scopul str3(ada, misu), arborele de deducie este cel prezentat n figura 6.

str3(ada, misu) parinte(ada, misu) INSUCCES str3(ada, Y), parinte(Y, misu) Y=Y' parinte(ada, Y') Y'=gelu parinte(ada, gelu) SUCCES
Figura 6. Arborele de deducie a scopului str3(ada, misu) ?- str4(ada, misu). % bucl infinita; eventual mesaj de depire a stivei
Pentru acest scop, arborele de deducie este cel prezentat n figura 7.

parinte(gelu, misu) SUCCES

str4(ada, misu) str4(ada, Y), parinte(Y, misu) str4(ada, Y'), parinte(Y', Y) str4(ada, Y"), parinte(Y", Y) ... arbore infinit
Figura 7. Arborele de deducie (infinit) a scopului str4(ada, misu)
Din punctul de vedere al semnificaiei procedurale, cele patru definiii nu sunt echivalente. Primele dou, str1 i str2, pot da rspuns pozitiv sau negativ la orice ntrebare, dar str2 este mai ineficient dect str1. Cititorul poate ncerca trasarea arborelui de deducie al satisfacerii scopului str2(ada, misu) i compararea acestuia cu cel corespunztor satisfacerii scopului str1(ada, misu). Definiia str4 produce o bucl infinit datorit intrrii infinite n recursivitate. Este evident o definiie greit din punct de vedere procedural. Definiia relaiei de strmo str3 este o definiie "capcan". Dup cum se poate observ din arborele de deducie prezentat, rspunsul sistemului este afirmativ n cazul n care exist o relaie de strmo ntre cele dou persoane argumente ale

30

predicatului. n cazul n care o astfel de relaie nu exist, sistemul intr ntr-o bucl infinit, cum ar fi, de exemplu, pentru persoanele mia i roco.

?- str1(mia, roco). no ?- str2(mia, roco). no ?- str3(mia, roco). % bucl infinit; eventual mesaj de depire a stivei
n acest caz, arborele de deducie al scopului str3(mia, roco) este prezentat n figura 8. Datorit semnificaiei procedurale a limbajului Prolog trebuie urmrit cu atenie modul de definire a unui predicat att din punctul de vedere al ordinii clauzelor, ct i din punctul de vedere al ordinii scopurilor n corpul regulilor.

str3(mia, roco) parinte(mia, roco) str3(mia, Y), parinte(Y, roco) str3(mia, Y'), parinte(Y', Y) str3(mia, Y"), parinte(Y", Y') ... arbore infinit

INSUCCES parinte(mia, Y)

INSUCCES parinte(mia, Y')

INSUCCES parinte(mia, Y") INSUCCES

Figura 8. Arbore de deducie infinit pentru un scop fals


Al doilea exemplu se refer la rezolvarea n Prolog a urmtoarei probleme. O maimu se gsete la ua unei camere i o banan se afl agat de plafon n centrul camerei. Lng fereastra camerei se afl o cutie pe care maimua o poate folosi pentru a se urca pe ea i a ajunge la banan. Maimua tie s fac urmtoarele micri: s mearg pe sol, s se urce pe cutie, s deplaseze cutia dac este lng cutie, s apuce banana dac este pe cutie i cutia este n centrul camerei. Se cere s se scrie un program Prolog care s descrie aceast problema i care s poat rspunde dac, dintr-o configuraie iniial specificat prin poziia maimuei i a cubului, maimua poate apuca banana dup execuia unei secvene de micri. Reprezentarea universului problemei n Prolog este specificat dup cum urmeaz. Starea iniial este:

(1) (2) (3) (4)

Maimua este la u. Maimua este pe sol. Cutia este la fereastr. Maimua nu are banana.

i poate fi descris prin structura Prolog:

31

stare(la_usa, pe_sol, la_fereastra, nu_are_banana) Starea final este aceea n care maimua are banana stare( _ , _ , _ , are_banana) Micrile cunoscute de maimu, deci operatorii de tranziie dintr-o stare n alta, sunt: (m1) Apuc banana. (m2) Urc pe cutie. (m3) Mut cutia. (m4) Merge (maimua se deplaseaz pe sol). urc mut(Poziia1, Poziia2) merge(Poziia1, Poziia2) apuc

i sunt reprezentate prin structurile Prolog indicate la dreapta micrilor.


Dintr-o anumit stare numai anumite micri sunt permise. De exemplu, maimua nu poate apuca banana dect dac este urcat pe cutie i cutia este n centrul camerei, adic sub banan. Micrile permise pot fi reprezentate n Prolog prin predicatul de deplasare depl cu trei argumente:

depl(Stare1, Micare, Stare2) Micare Stare1 Stare2 care transform problema din starea Stare1 n starea Stare2 prin efectuarea micrii legale Micare n starea Stare1. Se observ c reprezentarea aleas este o reprezentare a problemei folosind spaiul strilor [Flo93].
Soluia problemei este completat prin adugarea predicatului poatelua(Stare) care va reui dac maimua poate ajunge din starea iniial Stare ntr-o stare final n care poate lua banana, stare final descris de:

poate_lua(stare( _ , _ , _ , are_banana)).
Programul Prolog complet este prezentat n continuare.

% Structura stare: % stare(poz_o_maimuta, poz_v_maimuta, poz_cub, are_nu_are_banana) % Micri admise: apuc, urca, muta(Pozitia1, Pozitia2), merge(Pozitia1, Pozitia2), % reprezentate tot prin structuri. % Predicate: % depl(Stare1, Miscare, Stare2) % poate_lua(Stare) depl(stare(la_centru, pe_cutie, la_centru, nu_are_banana), apuca, stare(la_centru, pe_cutie, la_centru, are_banana)). depl(stare(P, pe_sol, P, H), urca, stare(P, pe_cutie, P, H)). depl(stare(P1, pe_sol, P1, H), muta(P1, P2), stare(P2, pe_sol, P2, H)). depl(stare(P1, pe_sol, B, H), merge(P1, P2), stare(P2, pe_sol, B, H)). poate_lua(stare( _ , _ , _ , are_banana)).

32

poate_lua(Stare1) :- depl(Stare1, Miscare, Stare2), poate_lua(Stare2).


La ntrebarea

?- poate_lua(stare(la_usa, pe_sol, la_fereastra, nu_are_banana)). yes sistemul rspunde afirmativ: maimua este fericit i mnnc banana.
O analiz atent a programului conduce la concluzia c programul d soluii numai pentru anumite situaii. n primul rnd, strategia de control a micrilor maimuei este impus de ordinea clauzelor care definesc predicatul depl. Astfel, maimua prefer nti s apuce, apoi s urce, apoi s mute cubul i numai la urm s mearga prin camer. Dac clauza corespunztoare micrii merge(Pozitia1, Pozitia2) ar fi fost pus ca prima clauz n definiia predicatului de deplasare, maimua ar fi mers la infinit prin camer fr s mai ajung sa mute cubul sau s apuce banana. Chiar pentru ordinea dat a clauzelor, dac se pune ntrebarea

?- poate_lua(stare(X, pe_sol, la_fereastra, nu_are_banana)). deci dac intereseaz din ce poziii maimua poate lua banana, rezolvarea dat nu este total satisfctoare deoarece programul are o infinitate de soluii. La cererea repetat a unei soluii, se va afia ntotdeauna valoarea: X = la_fereastra
Considernd din nou modelul spaiului strilor, se observ c n acest caz este vorba de un spaiu de cutare de tip graf n care o aceeai stare poate fi descoperit i redescoperit la infinit prin parcurgerea unui ciclu de tranziii de stri n acest graf. Aa cum este artat n [Flo93], astfel de cazuri trebuie tratate prin introducerea unor liste de stri parcurse (listele FRONTIERA i TERITORIU) care s mpiedice parcurgerea repetat a unor stri deja parcurse. Pericolul de apariie a unor cicluri infinite datorit parcurgerii unui spaiu de cutare graf nu este specific limbajului Prolog i poate s apar n orice implementare, n aceste cazuri. Ceea ce este neobinuit n raport cu alte limbaje este faptul c semnificaia declarativ a programului este corect, indiferent de ordonarea clauzelor, n timp ce programul este procedural incorect, avnd comportri diferite n funcie de aceast ordonare. Rezolvarea problemei ar mai trebui completat cu afiarea strilor i a micrilor executate de maimu pentru a ajunge n starea final n care ea poate apuca banana. Modaliti de eliminare a ciclurilor infinite de tipul celor ce apar n aceast problem vor fi discutate in partea a doua a lucrrii.

4.2

Controlul procesului de backtracking: cut i fail

Sistemul Prolog intr automat ntr-un proces de backtraking dac acest lucru este necesar pentru satisfacerea unui scop. Acest comportament poate fi n unele situaii deosebit de util, dar poate deveni foarte ineficient n alte situaii. Se consider urmtorul exemplu de program n care se definesc valorile unei funcii: f(X, 0) :- X < 3. f(X, 2) :- 3 =< X, X < 6. f(X, 4) :- 6 =< X. La ntrebarea: ?- f(1, Y).
33

%1 %2 %3

Y=0 sistemul rspunde indicnd c valoarea funciei pentru X=1 este Y=0. Dac se pune ntrebarea format din conjuncia de scopuri: ?- f(1, Y), 2 < Y. no sistemul semnaleaz eec. Arborii de deducie corespunztori acestor rspunsuri sunt prezentai n figura 9.

f(1,Y) X=1 Y=0 f(1,0) 1<3 SUCCES X=1 Y=0 f(1,0) 1=<3 2<0

f(1,Y), 2<Y X=1 Y=2 f(1,2) 3=<1 INSUCCES X=1 Y=4 f(1,4) 6=<1 INSUCCES

INSUCCES

Figura 9. Arborii de deducie a scopurilor f(1,Y) i f(1,Y),2 < Y


Se observ c se ncearc resatisfacerea primului scop cu regulile 2 i 3, dei acest lucru este evident inutil datorit semanticii acestor reguli. Cel care a scris aceste reguli poate cu uurin constata c, dac o valoare mai mic dect 3 pentru X duce la eecul scopului S din conjuncia de scopuri f(X, Y), S, este inutil s se ncerce resatisfacerea scopului f, deoarece aceasta nu este posibil [Bra88]. Se poate mpiedica ncercarea de resatisfacere a scopului f(X, Y) prin introducerea predicatului cut. Predicatul cut, notat cu atomul special !, este un predicat standard, fr argumente, care se ndeplinete (este adevrat) ntotdeauna i nu poate fi resatisfcut. Predicatul cut are urmtoarele efecte laterale: La ntlnirea predicatului cut toate seleciile fcute ntre scopul antet de regul i

cut sunt "ngheate", deci marcajele de satisfacere a scopurilor sunt eliminate, ceea ce duce la eliminarea oricror altor soluii alternative pe aceasta poriune. O ncercare de resatisfacere a unui scop ntre scopul antet de regula i scopul curent va eua.
Dac clauza n care s-a introdus predicatul cut reuete, toate clauzele cu acelasi

antet cu aceasta, care urmeaz clauzei n care a aprut cut vor fi ignorate. Ele nu se mai folosesc n ncercarea de resatisfacere a scopului din antetul clauzei care conine cut.
Pe scurt, comportarea predicatului cut este urmtoarea:

(C1) H :- D1, D2, , Dm, !, Dm+1, , Dn. (C2) H :- A1, , Ap.

34

(C3) H. Dac D1, D2, , Dm sunt satisfcute, ele nu mai pot fi resatisfcute datorit lui cut. Dac D1, , Dm sunt satisfcute, C2 i C3 nu vor mai fi utilizate pentru resatisfacerea lui H. Resatisfacerea lui H se poate face numai prin resatisfacerea unuia din scopurile Dm+1, , Dn, dac acestea au mai multe soluii.
Utiliznd predicatul cut, definiia funciei f(X, Y) poate fi rescris mult mai eficient astfel:

f(X, 0) :- X < 3, !. f(X, 2) :- 3 =< X, X < 6, !. f(X, 4) :- 6 =< X.


Predicatul cut poate fi util n cazul n care se dorete eliminarea unor pai din deducie care nu conin soluii sau eliminarea unor ci de cutare care nu conin soluii. El permite exprimarea n Prolog a unor structuri de control de tipul:

dac condiie

atunci aciune1 altfel aciune2

astfel: daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1. daca_atunci_altfel(Cond, Act1, Act2) :- Act2.
Se observ ns c exist dou contexte diferite n care se poate utiliza predicatul cut: ntr-un context predicatul cut se introduce numai pentru creterea eficienei programului, caz n care el se numete cut verde; n alt context utilizarea lui cut modific semnificaia procedural a programului, caz n care el se numete cut rou. Exemplul de definire a funciei f(X, Y) cu ajutorul lui cut este un exemplu de cut verde. Adugarea lui cut nu face dect s creasc eficiena programului, dar semnificaia procedural este aceeai, indiferent de ordinea n care se scriu cele trei clauze. Utilizarea predicatului cut n definirea predicatului asociat structurii de control daca_atunci_altfel introduce un cut rou deoarece efectul programului este total diferit dac se schimb ordinea clauzelor. Introducerea unui cut rou modific corespondena dintre semnificaia declarativ i semnificaia procedural a programelor Prolog. Se consider exemplul de definire a predicatului de aflare a minimului dintre dou numere, n urmtoarele dou variante:

min1(X, Y, X) :- X =< Y, !. min1(X, Y, Y) :- X > Y. min2(X, Y, X) :- X =< Y, !. min2(X, Y, Y).

% cut verde % cut rou

n definiia predicatului min1 se utilizeaz un cut verde; el este pus pentru creterea eficienei programului, dar ordinea clauzelor de definire a lui min1 poate fi schimbat fr nici un efect asupra rezultatului programului. n cazul predicatului min2 se utilizeaz un cut rou, asemntor structurii daca_atunci_altfel. Dac se schimb ordinea clauzelor de definire a predicatului min2:

min2(X, Y, Y). min2(X, Y, X) :- X =< Y, !.

35

rezultatul programului va fi evident incorect pentru valori X < Y.


n anumite cazuri efectul introducerii predicatului cut poate fi mai subtil. Se consider din nou definiia predicatului membru:

membru(X, [X | _]). membru(X, [ _ |Y]) :- membru(X, Y). i o definiie alternativ membru1(X, [X| _]) :- !. membru1(X, [ _ |Y]) :- membru1(X, Y).
Introducerea predicatului cut n definiia primei clauze a predicatului membru1 este justificat datorit creterii eficienei programului. Odat ce s-a descoperit c un element este membru al unei liste, este inutil ncercarea de resatisfacere a scopului. Cu toate acestea predicatul cut de mai sus nu este un cut verde, deoarece el schimb comportarea programului n cazul n care se pun ntrebri n care X este variabila neinstaniat n predicatele membru i membru1.

?- membru(X, [a, b, c]). X = a; X = b; X = c; no trei soluii pentru membru ?- membru1(X, [a, b, c]). X = a; no o soluie pentru membru1.
Efectul introducerii predicatului cut asupra semnificaiei declarative a programelor Prolog poate fi rezumat astfel:

p :- a, b. p :- c. p :- a, !, b. p :- c. p :- c. p :- a, !, b.

% Semnificaia declarativ este: % (a b) c % indiferent de ordinea clauzelor (n general). % Semnificaia declarativ este: % (a b) (~ a c). % Semnificaia declarativ este: % c (a b).

Oportunitatea utilizrii unui cut rou sau a unui cut verde este, dintr-un anumit punct de vedere, similar cu cea a utilizrii sau nu a salturilor n limbajele de programare clasic. Dac s-ar rescrie predicatul daca_atunci_altfel folosind un cut verde, definiia lui ar fi:

daca_atunci_altfel(Cond, Act1, Act2) :- Cond, !, Act1. daca_atunci_altfel(Cond, Act1, Act2) :- not (Cond), !, Act2.

36

unde predicatul not(Cond) este satisfcut dac scopul Cond nu este satisfcut. O astfel de definiie implic evaluarea lui Cond de dou ori i, dac Cond se definete ca o conjuncie de scopuri, posibil sofisticate, atunci ineficiena utilizrii unui cut verde n acest caz este evident. De la caz la caz, programatorul n Prolog trebuie s aleag ntre claritate, deci pstrarea corespondenei semnificaiei declarative cu cea procedural, i eficien.
n multe situaii intereseaz att condiiile de satisfacere a scopurilor, ct i condiiile de nesatisfacere a acestora, deci de eec. Fie urmtoarele fapte Prolog:

bun(gelu). bun(vlad). bun(mihai).


Pe baza acestor fapte se poate obine un rspuns afirmativ despre buntatea lui Gelu, Vlad i Mihai i un rspuns negativ dac se ntreab despre buntatea oricrei alte persoane. Din acest exemplu se poate vedea c limbajul Prolog folosete un model de raionament minimal numit ipoteza lumii nchise. Conform acestui model, tot ceea ce nu este tiut de program, deci afirmat explicit ca adevrat n program, este considerat fals. Limbajul Prolog permite exprimarea direct a eecului unui scop cu ajutorul predicatului fail. Predicatul fail este un predicat standard, fr argumente, care eueaza ntotdeauna. Datorit acestui lucru introducerea unui predicat fail ntr-o conjuncie de scopuri, de obicei la sfrit, cci dup fail tot nu se mai poate satisface nici un scop, determin intrarea n procesul de backtracking. Dac fail se ntlneste dup predicatul cut, nu se mai face backtracking. Enunul "Un individ este ru dac nu este bun." se poate exprima astfel:

bun(gelu). bun(vlad). bun(mihai). rau(X) :- bun(X), !, fail. rau(X). ?- rau(gelu). no ?- rau(mihai). no ?- rau(petru). yes
Dac predicatul fail este folosit pentru a determina eecul, cum este cazul exemplului de mai sus, este de obicei precedat de predicatul cut, deoarece procesul de backtracking pe scopurile care l preced este inutil, scopul eund oricum datorit lui fail. Exist cazuri n care predicatul fail este introdus intenionat pentru a genera procesul de backtracking pe scopurile care l preced, proces interesant nu din punctul de vedere al posibilitii de resatisfacere a scopului ce conine fail, ci din punctul de vedere al efectului lateral al acestuia.

rosu(mar). rosu(cub). rosu(soare). afisare(X) :- rosu(X), write(X), fail. afisare( _ ).


37

Scopul afisare(X) va afia toate obiectele roii cunoscute de programul Prolog datorit procesului de backtracking generat de fail; n acest fel s-a relizat prin fail o iteraie peste faptele rosu( ). Clauza afisare( _ ) este adugat pentru ca rspunsul final la satisfacerea scopului s fie afirmativ. n acest caz, introducerea unui cut nainte de fail ar fi determinat numai afiarea primului obiect rou din program. Revenind la combinaia !,fail, se consider n continuare implementarea n Prolog a afirmaiei: "Mihai iubete toate sporturile cu excepia boxului". Aceast afirmaie poate fi exprimat n pseudocod sub forma:

dac X este sport i X este box atunci Mihai iubete X este fals altfel dac X este sport atunci Mihai iubete X este adevrat i tradus n Prolog astfel: iubeste(mihai, X) :- sport(X), box(X), !, fail. iubeste(mihai, X) :- sport(X).
Predicatul cut utilizat aici este un cut rou. Combinaia !, fail este deseori utilizat n Prolog i are rolul de negaie. Se mai spune c limbajul Prolog modeleaz negaia ca eec al satisfacerii unui scop (negaia ca insucces), aceasta fiind de fapt o particularizare a ipotezei lumii nchise. Combinaia !, fail este echivalent cu un predicat standard existent n Prolog, predicatul not. Predicatul not admite ca argument un predicat Prolog i reuete dac predicatul argument eueaz. Utiliznd acest predicat, ultimul exemplu dat se poate exprima n Prolog astfel:

iubeste(mihai, X) :- sport(X), not(box(X)). iubeste(mihai, X) :- sport(X).


Un alt predicat standard este predicatul call, care admite ca argument un predicat Prolog i are ca efect ncercarea de satisfacere a predicatului argument. Predicatul call reuete dac predicatul argument reuete i eueaz n caz contrar. Utiliznd acest predicat, se poate explicita efectul general al predicatului standard not n urmtorul mod:

not(P) :- call(P), !, fail. not(P). Att predicatul not ct i predicatul call sunt predicate de ordinul II n Prolog deoarece admit ca argumente alte predicate. Seciunea urmtoare va prezenta mai multe astfel de predicate.

4.3 Predicate predefinite ale limbajului Prolog


Predicatele prezentate pn acum, cu excepia predicatului call, sunt predicate bazate pe logica cu predicate de ordinul I, modelul Prolog cu semnificaia declarativ a programelor urmrind ndeaproape modelul logic. n continuare se prezint o serie de predicate Prolog care nu mai pot fi asimilate cu logica cu predicate de ordinul I, respectiv: predicate ce decid asupra caracterului unor argumente, predicate de control a fluxului de execuie a programului i predicate de ordinul II, numite uneori i metapredicate. Metapredicatele accept ca argumente alte predicate Prolog, fapte sau reguli, i sunt interesante n special
38

prin efectul lateral pe care l produc. Toate tipurile de predicate menionate reprezint extinderea modelului programrii logice cu tehnici de programare care cresc puterea de calcul a limbajului. Majoritatea predicatelor prezentate n continuare sunt standard i se gsesc predefinite n orice implementare, o parte sunt specifice mediului ARITY Prolog, acestea fiind indicate ca atare. O implementare particular poate conine i alte predicate predefinite suplimentare celor prezentate aici. (a) Predicate de clasificare a termenilor
var(X) Predicatul var(X) reuete dac X este o variabil neinstaniat i eueaz n cazul n care X este o variabila instaniat sau o constant.

Exemple: ?- var(5). no ?- var(mihai). no ?- var(X). yes


novar(X) Predicatul novar(X) este adevrat dac X nu este o variabil neinstaniat. Acest predicat este opusul prdicatului var(X) i s-ar putea defini astfel:

novar(X) :- var(X), !, fail. novar( _ ).


atom(X) Predicatul atom(X) reuete dac X este un atom Prolog i eueaz n caz contrar.

Exemple: ?- atom(coco). yes ?- atom(100). no ?- atom(carte(prolog, clocksin)). no


integer(X) Predicatul integer(X) reuete dac X este un numr ntreg. Se consider urmatorul exemplu de program Prolog care transform o expresie reprezentat printr-o sum de variabile i constante ntr-o expresie care conine suma variabilelor plus o constant care reprezint suma tuturor constantelor din expresie [CM84]. Predicatul simplif are dou argumente: primul argument este forma iniial a expresiei, argument instaniat, iar cel de al doilea reprezint expresia simplificat, sintetizat de program. 39

% simplif(Expresie_iniial, Expresie_simplificat) % Predicat ajutator simpl % simpl(Ex_iniial, Suma_acumulat, Ex_simplificat). simplif(Expr, Expr1) :- simpl(Expr, 0, Expr1). simpl(Expr + A, N, Expr1) :integer(A), !, N1 is N + A, simpl(Expr, N1, Expr1). simpl(Expr + A, N, Expr1 + A) :atom(A), !, simpl(Expr, N, Expr1). simpl(Rest, N, N1) :integer(Rest), !, N1 is Rest + N. simpl(Rest, 0, Rest) :- !. simpl(Rest, N, N + Rest). La ntrebarea ?- simplif(x + 40 + y + 1 + 55 + x + 2, E). programul rspunde E = 98 + x + y + x
n programul de mai sus definirea predicatului simplif s-a fcut pe baza unui predicat ajuttor simpl care conine un argument suplimentar. Acest argument, instaniat la 0 n cazul primului apel, este folosit ca o variabil de acumulare pentru calculul sumei constantelor ntregi din expresie. atomic(X) Pe baza predicatelor atom(X) i integer(X) se poate defini predicatul atomic(X) care este adevrat dac X este fie ntreg, fie atom Prolog:

atomic(X) :- atom(X). atomic(X) :- integer(X).


De multe ori predicatul atomic(X) este predefinit n sistem.

(b)

Predicate de control

Predicatul true reuete ntotdeauna, iar fail eueaz ntotdeauna. Predicatul repeat simuleaz structurile de control repetitive din limbajele clasice de programare. El are o infinitate de soluii, putndu-se resatisface de oricte ori este nevoie, fr a umple stiva. Definiia lui ar putea fi:

repeat. repeat :- repeat.


Acest predicat este predefinit in mediul ARITY. n ARITY Prolog exist i alte predicate predefinite care simuleaz structurile de control din programarea procedural: ifthen(+Conditie, +Scop), pentru dac-atunci; ifthenelse(+Conditie, +Scop-then, +Scop-else), 40

pentru dac-atunci-altfel; i case([Conditie1 -> Scop1, Conditien -> Scopn | Scop-default), sau case([Conditie1 -> Scop1, Conditien -> Scopn), pentru case. O predicat echivalent ciclului for, care nu este prefedinit n ARITY, se poate implementa astfel:

% for(Variabla, ValoareInitiala, ValoareFinala, Pas) for(I, I, I, 0). for( _, _ , _ , 0) :- !, fail. for(I, I, F, S) :S > 0, (I > F, !, fail ; true) ; S < 0, (I < F, !, fail ; true). for(V, I, F, S) :- I1 is I + S, for(V, I1, F, S). Exemple de apeluri: a :- for(X, 10, -10, -3.5), write(X), tab(2), fail ; true. b :- for(X, -10, 10, 3.5), write(X), tab(2), fail ; true. c :- for(X, 10, 10, 0), write(X), tab(2), fail ; true. ?- a. 10 6.5 3.0 -0.5 -4.0 -7.5 yes ?- b. -10 -6.5 -3.0 0.5 4.0 7.5 yes ?- c. 10 yes Predicatul for eueaz pentru apeluri incorecte (cicluri infinite) de genul: for(X, -10, 10, 0). for(X, 10, -10, 2). for(X, -10, 10, -2). (c) Predicate de tratare a clauzelor drept termeni
Predicatele din aceast categorie permit: construirea unei structuri care reprezint o clauz n baza de cunotinte, identificarea clauzelor din program, adugarea sau eliminarea unor clauze, reprezentate printr-o structur, n baza de cunotine a sistemului. clause(Antet, Corp) Satisfacerea scopului clause(Antet, Corp) necesit unificarea argumentelor Antet i Corp cu antetul i corpul unei clause existente n baza de cunotine Prolog. La apelul acestui predicat, Antet trebuie s fie instaniat astfel nct predicatul principal al clauzei s fie cunoscut. Dac nu exist clauze pentru acest predicat, scopul eueaz. Dac exist mai multe clauze care definesc predicatul Antet, scopul clause(Antet, Corp) va avea mai multe soluii, fiecare soluie corespunznd unei clauze de definire a predicatului Antet. Dac o clauz este fapt, Corp se instaniaz la constanta true.

Exemplu. Considernd definiia predicatului de concatenare a dou liste ca fiind deja introdus n baza de cunotine Prolog:
41

% conc(Lista1, Lista2, ListaRez) conc([], L, L). conc([Prim|Rest1], L2, [Prim|Rest3]) :- conc(Rest1, L2, Rest3). se obin urmtoarele rezultate: ?- clause(conc(A, B, C), Corp). A = [ ], B = _004C, C = _004C, Corp = true; A = [_00F0|_00F4], B = _004C, C = [_00F0|_00FC], Corp = conc (_00F4, _004C, _00FC); no
Utiliznd predicatul clause se poate defini un predicat listc de afiare a clauzelor care definesc un predicat:

% listc(Antet) - afieaz toat clauzele din baza de cunotine % care definesc scopul Antet listc(Antet) :clause(Antet, Corp), afis(Antet, Corp), write('.'), nl, fail. listc( _ ). afis(Antet, true) :- !, write(Antet). %1 afis(Antet, Corp) :- write(Antet), write(':'), write('-'), write(Corp). Efectul acestui program, coinsidernd definiia anterioar a scopului conc, este urmtorul: ?- listc(conc(_, _, _)). conc([], _0034, _0034). conc([_0108|_010C], _0034, [_0108|_0114]:-conc(_010C, _0034, _0114).
n definirea primei clauze a predicatului afis utilizarea predicatului cut este esenial deoarece afiarea clauzelor este bazat pe procesul de backtracking generat intenionat de introducerea predicatului fail n prima clauz a predicatului listc. Regula %1 din predicatul afis este necesar deoarece faptele sunt reguli avnd corpul format din scopul true. Aceast reprezentare ne arat c faptele i regulile au aceeai reprezentare n Prolog (toate reprezint clauze).

Exemplu. Pentru urmtorul predicat: p(1). p(X) :- X > 2, X < 5. p(X). predicatul clause d urmtoarele rspunsuri: ?- clause(p(X), Corp). X = 1, Corp = true ;
42

X = _0038, Corp = _0038 > 2 , _0038 < 5 ; X = _0038, Corp = true.


asserta(Clauza), assertz(Clauza), assert(Clauza), Predicatul asserta(Clauza) reuete ntotdeauna o singur dat i are ca efect lateral adugarea clauzei Clauza la nceputul bazei de cunotine Prolog. Predicatele assertz(Clauza) i assert(Clauza) au o comportare similar, cu excepia faptului c argumentul Clauza este adugat la sfritul bazei de cunotine. Argumentul Clauza trebuie s fie instaniat la o valoare ce reprezint o clauz Prolog. Aciunea de adugare a unei clauze prin asserta, assert sau assertz nu este eliminat de procesul de backtracking. Clauza adugat va fi eliminat numai n urma unei cereri explicite prin intermediul predicatului retract. retract(Clauza) Predicatul retract(Clauza) are ca efect eliminarea din baza de cunotine a unei clauze care unific cu argumentul Clauza. Predicatul reuete dac o astfel de clauza exista; la fiecare resatisfacere se elimin din baza de cunotine o nou clauz care unific cu argumentul. Clauza trebuie sa fie argument instaniat la apel. Odat ce o clauz este eliminat din baza de cunotine prin retract, clauza nu va mai fi readaugat dac se face backtracking pe retract.

Exemplu. adaug :- asserta(prezent(nero)), asserta(prezent(cezar)). scot :-retract(prezent(nero)). actiune1 :- adaug, listc(prezent(X)). actiune2 :- scot, listc(prezent(X)). ?- actiune1. prezent(nero). prezent(cezar). ?- actiune2. prezent(cezar).
Utiliznd predicatul retract, se poate defini un predicat care are rolul de a elimina din baza de cunotine toate clauzele care definesc un predicat. Acest predicat, numit retractall(Antet), este predefinit n anumite implementri Prolog. Definiia lui pe baza predicatului retract este:

% retractall(Antet) - elimin toate clauzele care definesc scopul Antet retractall(Antet) :- retract(Antet), fail. retractall(Antet) :- retract( (Antet :- Corp) ), fail. retractall( _ ). Observaie: n ARITY Prolog, atunci cnd se adaug/elimin o regul n/din baza de date n mod dinamic cu assert/retract regula trebuie pus ntre paranteze rotunde sau dat ca o structur: assert( (p(X) :- X=1 ; X=2) ). retract( ':-'(p(X), ';'(X=1, X=2))).
Predicatele asserta, assertz i retract pot fi utile n diverse situaii datorit efectelor lor laterale. Ele pot simula, de exemplu, un fel de mecanism de variabile globale n Prolog. Se 43

consider exemplul de implementare a unui generator de numere aleatoare n Prolog. Se definete predicatul aleator(R, N) care genereaz un numr aleator N n intervalul [1..R] pornind de la o smn fixat i folosind metoda congruenei liniare. De fiecare dat cnd trebuie generat un numr aleator, valoarea acestuia este determinat folosind smna existent i o nou smn este calculat i memorat ca fapt Prolog pn la noul apel. Vechea smn este eliminat din baza de cunotine .

% aleator(R, N) - instaniaz N la un numr aleator ntre 1 i R samanta(11). aleator(R, N) :samanta(S), modul(S, R, N1), N is N1 + 1, retract(samanta(S)), N2 is (125 * S +1), modul(N2, 4096, SamantaNoua), asserta(samanta(SamantaNoua)). modul(X, Y, X) :- X < Y. modul(X,Y,Z) :% Predicatul modulo este predefinit X >= Y, % n anumite implementri X1 is X - Y, modul(X1, Y, Z).
La apelul predicatului se obin urmtoarele rezultate:

?- aleator(100, N). N = 14 ?- aleator(100, N). N = 27 ?- aleator(100, N). N = 48


Dac se dorete afiarea continu a numerelor aleatoare, o prim variant este aceea de a crea un ciclu infinit de afiare a acestor numere, efect realizat de predicatul nr_aleat(R) care afieaz numere aleatoare n intervalul [1..R].

% nr_aleat(R) - afieaz la infinit numere aleatoare ntre 1 i R nr_aleat(R) :- repeat, aleator(R, X), write(X), nl, fail.
Predicatul fail introdus n definiia predicatului nr_aleat(R) determin intrarea n procesul de backtracking i datorit predicatului repeat, cele dou scopuri aleator(R, X) i write(X) se vor satisface la infinit. Trebuie observat faptul c numai scopul repeat se resatisface; scopurile aleator i write sunt la prima satisfacere de fiecare dat. n cazul n care se dorete construirea unui ciclu care se va termina dac o anumit condiie este ndeplinit, se poate reformula predicatul de generare a numerelor aleatoare astfel:

nr_aleat1(R) :repeat, aleator(R, X), write(X), nl, write('Continuati? [d/n]'), read('n').


44

Apelnd predicatul se va obine urmtoarea secven de numere aleatoare:

?- nr_aleat1(10). 4 Continuati? d. 7 Continuati? d. 8 Continuati? n. yes


functor(Term, Funct, N) Predicatul reuete dac cele trei argumente unific astfel: Term este o structur cu functor Funct i aritate N. Predicatul poate fi folosit n dou moduri:

(1)

Term este argument instaniat i predicatul reuete dac Term este atom sau structur i eueaz n caz contrar. n caz de succes, Funct i N sunt instaniate la functorul, respectiv aritatea lui Term. Un atom este considerat ca fiind o structur de aritate 0. Term este argument neinstaniat iar Funct i N sunt instaniate, specificnd functorul i numrul de argumente. n acest caz scopul reuete ntotdeauna i Term va fi instaniat la o structur cu functorul i numrul de argumente specificate. Argumentele structurii construite n acest mod sunt neinstaniate.

(2)

Exemple: ?- functor(auto(honda, culoare(roie)), Funct, N). Funct = auto, N = 2 ?- functor(a + b, F, N). F = +, N = 2 ?- functor(albastru, F, N). F = albastru, N = 0 ?- functor [a, b, c], !, 3). no ?- functor(Term, pasi_plan, 3). Term = pasi_plan(_0, _0, _0)
arg (N, Term, Arg) Predicatul arg (N, Term, Arg) are ca efect obinerea celui de al N-lea argument al unei structuri Term, acest argument devenind instanierea lui Arg. N i Term trebuie s fie argumente instaniate. Arg poate fi instaniat, caz n care predicatul reuete dac Arg este argumentul N din structura Term, sau Arg poate fi neinstaniat, caz n care se va instania la cel de al N-lea argument al lui Term.

?- arg(2, poseda(mihai, frumoasa(portocala)), Arg). Arg = frumoasa(portocala).


45

?- arg (2,[a, b, c], X). X = [b, c] ?- arg(1, a + (b + c), b). no


Scop = .. [Functor | ListArg] Predicatul =.., numit univ, este un predicat folosit ca operator infixat. El poate fi folosit n trei moduri:

(1)

Scop este instaniat. n acest caz predicatul reuete, dac Scop este o structur, iar Functor i ListArg se instaniaz la functorul, respectiv lista argumentelor acelei structuri. Scop este neinstaniat, iar Functor i ListArg sunt instaniate la un functor, respectiv la o list de argumente a unei structuri. Dac valorile lui Functor i ListArg sunt corecte, predicatul reuete i Scop va fi instaniat la structura creat cu functorul i lista de argumente date. Toi trei parametrii sunt instaniati i predicatul reuete dac Functor i ListArg sunt functorul, respectiv lista argumentelor structurii Scop.

(2)

(3)

Acest predicat are o importan deosebit n Prolog deoarece el permite sinteza dinamic a scopurilor Prolog, deci construirea de clauze pe parcursul execuiei programului. Lucrul acesta este posibil deoarece clauzele Prolog au tot forma unor structuri formate din functor i argument, sintaxa codului fiind aceeai cu sintaxa datelor n Prolog.

Exemple: ?- sir(a, b, c) =.. X. X = [sir, a, b, c] ?- (f(a) + g(b)) =.. [+, f(X), Y]. X = a, Y = g(b) ?- a + b + c =.. L. L=[+, a+b, c] ?- a * b + c =.. L. L = [+, a * b, c] ?- a + b * c =.. L. L = [+, a, b * c] % a + b + c = '+'(a, '+'(b, c)) % a * b + c = '+'('*'(a, b), c) % a + b * c = '+'(a, '*'(b, c))

?- Scop =.. [parinte, mihai, gina]. Scop = parinte(mihai, gina) ?- Scop =.. [membru, a, [a, b, c]]. Scop = membru(a, [a, b, c]) ?- conc([1, 2], [3], [1, 2, 3]) =.. [Functor | ListArg]
46

Functor = conc, ListArg = [[1, 2], [3], [1, 2, 3]] ?- S =.. [f, X, a,Y]. S = f( _004C, a, _0060 ), X = _004C, Y = _0060 ?- f =.. L. L = [f]
Folosind univ putem verifica faptul c listele se reprezint prin structuri cu punct de forma: '.'(PrimulElement, RestulListei). Lista [1, 2] este totuna cu '.'(1, [2]) sau cu '.'(1, '.'(2, []).

Exemplu. ?- [1,2] =.. L. L = [. , 1, [2]].


listing, listing(+Nume), listing(+Nume/Aritate), listing(+[Nume/Aritate]) Predicatul listing tiprete din baza de cunotine toate clauzele, toate clauzele unui predicat sau toate clauzele unor predicate dintr-o list. De exemplu, pentru predicatele:

p. p(1). p(X) :- X > 2, X < 5. p(X). p(1,2). p(X,Y) :- X < Y. se pot face urmtoarele interogri (redenumirile variabilelor sunt fcute automat de sistem): ?- listing(p/1). p(1). p(A) :- A > 2, A < 5. p(A). yes ?- listing([p/0, p/2]). p. p(1,2). p(A, B) :- A < B. yes Predicate pentru execuia dinamic a scopurilor
call(Scop) Se presupune c argumentul Scop este instaniat la un scop Prolog. Predicatul call(Scop) reuete dac Scop reuete i eueaz n caz contrar. La prima vedere acest predicat pare redundant deoarece execuia scopului Scop se poate face direct n Prolog. Dac se doreste execuia dinamic a 47

(d)

scopurilor care au fost sintetizate pe parcursul execuiei unui program Prolog, atunci predicatul call este necesar. n plus, dac nu se cunoate scopul de executat, de exemplu se dorete execuia a diverse scopuri cu argumente diferite sau cu aceleai argumente, se poate folosi predicatul call cu argument o variabil care se va instania n funcie de ntrebarea utilizatorului sau de context. Execuia dinamic a scopurilor poate fi exprimat prin urmtoarea secven de scopuri:

... obine(Functor), calculeaz(ArgList), Scop =.. [Functor | ArgList], call(Scop), ...


Se prezint n continuare diverse variante de predicate Prolog care aplic un predicat primit ca argument fiecrui element al unei liste sau al mai multor liste, rezultatele fiecrei aplicri fiind cumulate ntr-o list rezultat. Acest tip de predicate reprezint o categorie de prelucrri ale listelor, frecvent ntlnite i necesare. Se definete nti predicatul mapcar(Pred, ListaArg, ListaRez) care primete dou argumente instaniate Pred i ListaArg i calculeaz ListaRez. Pred este un predicat Prolog care are la rndul lui dou argumente: primul, instaniat, este folosit n prelucrare pentru obinerea celui de al doilea i este obinut ca element curent din ListaArg; cel de al doilea, neinstaniat, este calculat de Pred i este depus ca element curent n ListaRez. n programul care urmeaz se vor folosi ca predicate Pred urmtoarele:

prim(Lista, El) care obine primul element dintr-o list, neg(Element, -Element) care schimb semnul unui element i adaug(Nume, NumeFrumos) care adaug n faa unui nume apelativul de politee dna dac persoana este femeie i dl dac persoana este brbat. Se observ c argumentele lui Pred din definiia lui mapcar pot fi att elemente atomice (atomi sau numere) ct i liste. % Se definesc predicatele de prelucrare a listelor: mapcar, mapcar2 i mapcarnl. prim([], []). prim([X | _ ], X). neg(X, -X). adaug(Nume, NumeFrumos) :write(Nume), write(' este femeie sau barbat (f/b)? '), read(Sex), (Sex = b, NumeFrumos = [dl, Nume] ; Sex = f, NumeFrumos = [dna, Nume]). % mapcar(Pred, ListaArg, ListaRez) - evalueaz predicatul % Pred pentru fiecare element din lista ListaArg i % construiete rezultatul n ListaRez. % Predicatul Pred are dou argumente, atomice sau liste. mapcar( _ , [], []). mapcar(P, [Arg | RestArg], [X | Rest]) :Scop =.. [P, Arg, X], call(Scop), mapcar(P, RestArg, Rest).

48

n definirea scopului adaug s-a utilizat simbolul ;. Acesta este un operator predefinit n Prolog care marcheaz o disjuncie de scopuri, deci are semnificaia de disjuncie logic. Efectul operatorului ; poate fi reprezentat n urmtorul fel:

X ; X :- X. X ; Y :- Y. Deci aa cum operatorul Prolog , corespunde unei conjuncii de scopuri, operatorul ; corespunde unei disjuncii de scopuri.
Comportarea acestui program este urmtoarea:

?- mapcar(prim, [[alain, turing], [ada, byron]], Rez). Rez = [alain, ada] ?- mapcar(neg, [1, 2, 3, 4], Rez). Rez = [-1, -2, -3, -4] ?- mapcar(adaug, [maria, mihai, george, ana], Rez). maria este femeie sau barbat (f/b)? f mihai este femeie sau barbat (f/b)? b george este femeie sau barbat (f/b)? b ana este femeie sau barbat (f/b)? f Rez = [[dna, maria], [dl, mihai], [dl, george], [dna, ana]]
Dac se doreste execuia unor prelucrri similare, dar pentru predicate Prolog care admit trei argumente, primele doua instaniate i cel de al treilea sintetizat de predicatul Pred pe baza primelor dou, se poate defini de o maniera similar predicatul:

mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez)


Se va demonstra funcionarea acestui predicat n cazurile n care predicatul Pred, care se mapeaz pe elementele listelor ListaArgPrim i ListaArgSecund, este nti plus(A, B, Rez) care calculeaz n Rez suma lui A i B, apoi conc(List1, Lista2, ListaRez) care concateneaz dou liste i depune rezultatul n ListaRez. Argumentele predicatului Pred pot fi argumente atomice sau liste.

conc([], L, L). conc([X|Rest], L, [X|Rest1]) :- conc(Rest, L, Rest1). plus(A, B, Rez) :- Rez is A + B. % mapcar2(Pred, ListaArgPrim, ListaArgSecund, ListaRez) % evalueaz predicatul Pred pentru fiecare pereche % de argumente, unul din ListaArgPrim, cellalt din ListaArgSecund % i construiete rezultatul n ListaRez. % Predicatul Pred are trei argumente, atomice sau liste. mapcar2( _ , [], _ , []). mapcar2(P, [Arg1 | RestArg1], [Arg2 | RestArg2], [X | Rest]) :Scop =.. [P, Arg1, Arg2, X], call(Scop), mapcar2(P, RestArg1, RestArg2, Rest). ?- mapcar2(plus, [1, 2, 3, 4], [10, 20, 30, 40], Rez).
49

Rez = [11, 22, 33, 44] ?- mapcar2(conc, [[1, 2], [a, b]], [[3, 4], [c, d]], Rez). Rez = [[1, 2, 3, 4], [a, b, c, d]]
Se observ c este necesar definirea unui predicat de tip mapcar diferit pentru fiecare categorie de aritate N a predicatelor care pot instania legal Pred. Dac se impune restricia conform creia predicatul Pred poate avea numai argumente atomice, atunci se poate defini predicatul mapcarnl(Pred, ListaListeArg, ListaRez) cu un efect similar. ListaListeArg este fie o list de elemente atomice, dac Pred are dou argumente, dintre care primul instaniat va fi obinut ca elementul curent al acestei liste, fie o list de liste, unde fiecare element list din ListaListeArg conine primele N-1 argumente atomice ale lui Pred.

% mapcarnl(Pred, ListaListeArg, ListaRez) % evalueaz predicatul Pred cu primele N-1 argumente din lista % ListaListeArg i construiete rezultatul n ListaRez. % Predicatul Pred poate avea oricte argumente, dar toate trebuie sa fie atomice. mapcarnl(_, [], []). Mapcarnl(P, [Arg|RestArg], [X|Rest]) :(atomic(Arg), FormaScop = [P,Arg,X] ; not atomic(Arg), conc([P], Arg, Temp), conc(Temp, [X], FormaScop)), Scop =.. FormaScop, call(Scop), mapcarnl(P, RestArg, Rest). ?- mapcarnl(neg, [1, 2, 3, 4], Rez] Rez = [-1, -2, -3, -4] ?- mapcarnl[[1, 10], [2, 20], [3, 30], [4, 40]], Rez). Rez = [11, 22, 33, 44].
findall(X, Scop, Lista) Predicatul findall(X, Scop, Lista) are ca efect obinerea tuturor termenilor X care satisfac predicatul Scop n baza de cunotine a programului i cumularea acestor termeni n lista Lista. Scop trebuie s fie instaniat la un predicat Prolog n care, n mod normal, trebuie s apar X. Dac Scop nu reuete, findall reuete, instaniind Lista la lista vid (aceast ultim caracteristic este specific mediului ARITY).

Exemple: prieten(vlad, ana). prieten(vlad, george). prieten(vlad, mihai). prieten(liviu, ana). ?- findall(X, prieten(vlad, X), L). X = _0038, L = [ana, george, mihai]
Efectul predicatului findall poate fi explicat i prin definiia explicit a unui predicat cu acelai efect: find_all(X, Scop, Lista)

% find_all(Arg, Scop, Lista) 50

% are acelai efect ca predicatul standard findall(Arg, Scop, Lista). prieten(vlad, ana). prieten(vlad, george). prieten(vlad, mihai). prieten(liviu, ana). find_all(X, S, _) :- asserta(stiva(baza_stivei)), call(S), % Scopul S conine X. asserta(stiva(X)), fail. find_all( _ , _ , L) :- colecteaza([], M), !, L = M. colecteaza(S, L) :- urmator(X), !, colecteaza([X | S], L). colecteaz(L, L). urmtor(X) :- retract(stiva(X)), !, X \= baza_stivei. ?- find_all(X, prieten(vlad, X), L). X = _0038, L = [ana, george, mihai]. ?- find_all(X, prieten(toma, X), L). X = _0038, L = []
n definiia predicatului find_all se observ utilizarea unei tehnici de programare Prolog interesante. Utiliznd predicatul asserta se simuleaz o structur de stiv n Prolog. Baza stivei este marcata de faptul stiva(baza_stivei) i n stiv se introduc, ca fapte suplimentare adugate la nceputul bazei de cunotine Prolog, valorile X care satisfac scopul S sub forma stiva(X), cu X instaniat de apelul call(S). Acest lucru este executat pentru toate soluiile posibile ale predicatului call(S) datorit predicatului fail introdus n finalul definiiei primei clauze a predicatului find_all. n acest fel toate valorile lui X din baza de cunotine Prolog care satisfac S sunt introduse n stiva simulat. Cea de a doua clauz a predicatului find_all, care se execut dup ce nu mai exist nici o posibilitate de resatisfacere a primeia, are ca scop golirea stivei i colectarea valorilor introduse n stiv, pn la baza ei, stiva(baza_stivei), n lista M. bagof(X, Scop, Lista) Predicatul bagof(X, Scop, Lista) are ca efect colectarea n Lista a tuturor termenilor X care satisfac Scop, innd cont de diversele instanieri posibil diferite ale celorlalte variabile din Scop. Scop este un argument care trebuie s fie instaniat la un scop Prolog. Dac Scop nu conine alte variabile n afara lui X, atunci efectul predicatului bagof este identic cu cel al lui findall. Dac Scop nu reuete cel puin o dat atunci bagof eueaz.

Exemple: clasificare(a, vocala). clasificare(b, consoana). clasificare(c, consoana). clasificare(d, consoana). clasificare(e, vocala).
51

clasificare(f, consoana). ?- findall(X, clasificare(X, C), L). X = _0038, C = _004C, L = [a, b, c, e, f] ?- bagof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [b, c, d, f]; X = _0038, C = vocala, L = [a, e] no
setof(X, Scop, Lista)

% o soluie

% dou soluii

Predicatul setof(X, Scop, Lista) are acelai efect cu predicatul bagof, cu excepia faptului c valorile cumulate n lista Lista sunt sortate cresctor i se elimin apariiile multiple de valori, deci Lista devine o mulime ordonat.Dac Scop nu reuete cel puin o dat atunci setof eueaz. Considernd faptele de definire a consoanelor i variabilelor prezentate anterior, se poate construi urmtorul exemplu, n care mai introducem dou fapte:

asserta(clasificare(d, consoana)). asserta(clasificare(e, vocala)). ?- bagof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [d, b, c, d, f]; X = _0038, C = vocala, L = [e, a, e]; no ?- setof(X, clasificare(X, C), L). X = _0038, C = consoana, L = [b, c, d, f]; X = _0038, C = vocala, L = [a, e]; no

% consoana d apare de dou ori % vocala e apare de dou ori

% consoanele apar o singur dat % vocalele apar o singur dat

4.4

Direcia de construire a soluiilor

Majoritatea programelor Prolog se bazeaz pe recursivitate. Ca n orice limbaj de programare recursiv, parametrii de ieire ai subprogramelor, deci argumentele sintetizate ale predicatelor n cazul limbajului Prolog, pot fi calculate fie pe ramura de avans n recursivitate, fie pe ramura de revenire din recursivitate. Se consider dou variante de definire a predicatului de numrare a elementelor dintr-o list. Prima varianta, nr_elem(Lista, NrElem), construiete soluia (calculeaz numrul de elemente din list) pe ramura de revenire din recursivitate. % nr_elem(Lista, NrElem) nr_elem([], 0). nr_elem([ _ | Rest], N) :- nr_elem(Rest, N1), N is N1 + 1.
A doua variant, nr_elem2(Lista, NrElem) calculeaz numrul de elemente din list pe ramura de avans n recursivitate. Pentru a realiza acest lucru, se folosete un predicat ajuttor nr_elem1(Lista, NrAcumulat, NrElem) care are un argument suplimentar fa de predicatul nr_elem2. Acest argument, NrAcumulat, are rolul de variabil de acumulare a numrului de elemente din list pe msura avansului n recursivitate i este instaniat la primul apel la valoarea 0. 52

n momentul atingerii punctului de oprire din recursivitate, deci n cazul n care lista ajunge vid, valoarea acumulata n argumentul NrAcumulat este copiat n cel de-al treilea parametru al predicatului nr_elem_1. Se face apoi revenirea din apelurile recursive succesive fr a efectua nici o alt prelucrare, predicatul nr_elem_1 reuete i trimite valoarea calculat n NrElem predicatului iniial nr_elem2.

% nr_elem2(Lista, NrElem) % nr_elem1(Lista, NrAcumulat, NrElem) nr_elem2(Lista, N) :- nr_elem1(Lista, 0, N). nr_elem1([], N, N). nr_elem1([ _ | Rest], N1, N2) :- N is N1 + 1, nr_elem1(Rest, N, N2).
O abordare similar se poate vedea n cazul celor dou variante de definire a predicatului de intersecie a dou liste (determinarea elementelor comune celor dou liste). Prima variant, inter(Lista1, Lista2, ListaRez), calculeaz soluia (lista intersecie) pe ramura de revenire din recursivitate. Cea de a doua variant, int(Lista1, Lista2, ListaRez), calculeaz soluia pe ramura de avans n recursivitate, utiliznd int1(Lista1, Lista2, ListaAcumulare, ListaRez) ca predicat ajuttor cu parametrul suplimentar ListaAcumulare, instaniat la primul apel la lista vid.

% inter(Lista1, Lista2, ListaRez) member(Elem, [Elem|_]) :- !. member(Elem, [_|Rest]) :- member(Elem, Rest). inter([], _, []). inter([Prim|Rest], Lista2, [Prim|LRez]) :member(Prim, Lista2), !, inter(Rest, Lista2, LRez). inter([ _ | Rest], Lista2, LRez) :- inter(Rest, Lista2, LRez). % int(Lista1, Lista2, ListaRez) % int1(Lista1, Lista2, ListaAcumulare, ListaRez) int(L1, L2, LRez) :- int1(L1, L2, [], LRez). int1([], _, L, L). int1([Prim|Rest], L, L1, L2) :member(Prim,L), !, int1(Rest, L, [Prim | L1], L2). int1([ _ | Rest], L, L1, L2) :- int1(Rest, L, L1, L2).
Aceast tehnic de programare, des ntlnit n programele Prolog, are o serie de avantaje, printre care o claritate crescut i, n principal, utilizarea definiiilor de predicate recursive la urm. Un predicat recursiv la urm este un predicat n care apelul recursiv al acestuia este ultimul scop din conjuncia de scopuri care l definete i pentru care nu mai exist nici o regul posibil de aplicat dup apelul recursiv. Un astfel de predicat are avantajul c poate fi apelat recursiv de oricte ori fr a genera o depire a stivei. n plus execuia unui astfel de predicat este mai eficient. Pentru a pune n eviden aceast comportare se definesc patru variante ale unui predicat care afieaz (numr) valori ntregi succesive la infinit, ncepnd de la o valoare fixat N.

% Predicatele numr(N), numr1(N), ru_numr(N) i ru_numr1(N) % afieaz ntregi succesivi pornind de la valoarea N.
53

numara(N) :- write(N), nl, N1 is N + 1, numara(N1). numara1(N) :- N >= 0, !, write(N), nl, N1 is N + 1, numara1(N1). numara1( _ ) :- write("Valoare negativa."). rau_numara(N) :- write(N), nl, N1 is N + 1, rau_numara(N1), nl. rau_numara1(N) :- N >= 0, write(N), nl, N1 is N + 1, rau_numara1(N1). rau_numara1(N) :- N < 0, write("Valoare negativa.").
Primele dou variante ale acestui predicat, numara(N) i numaraa(N), sunt predicate recursive la urm. Predicatul numara(N) este definit printr-o singur regul i apelul recursiv este ultimul scop al definiiei. Predicatul numara1(N) este definit prin dou reguli, dar, datorit predicatului cut din prima regul, dac N 0 nu mai exist nici o alt regul posibil de aplicat n momentul apelului recursiv. Ambele variante vor numra la infinit ncepnd de la N i afind valori succesive. Urmtoarele dou variante, rau_numara(N) i rau_numara1(N), nu sunt predicate recursive la urm. Predicatul rau_numara(N) nu are apelul recursiv ca ultim scop n definiie, iar rau_numara1(N) are o variant (regula) nencercat n momentul apelului recursiv. Execuia ambelor predicate se va opri dup un anumit numr de valori afiate, cu afiarea unui mesaj de eroare cauzat de depirea stivei.

54

55

Partea a II-a Aplicaii


n continuare se vor prezenta o serie de mecanisme de prelucrare specifice limbajului Prolog. Anumite programe implementeaz algoritmi i prelucrri de structuri de date presupuse cunoscute, cum ar fi arbori sau metode de sortare. Alte programe, cum ar fi cele de cutare n spaiul strilor sau n arbori i/Sau, sunt specifice inteligenei artificiale i este recomandat parcurgerea capitolelor corespunztoare din [Flo93] unde se descriu principiile de baz ale acestor aplicaii.

Mediul ARITY Prolog

n aceast seciune se prezint comenzile de editare i depanare din mediul de programare ARITY Prolog i predicatele predefinite n aceast versiune.

5.1

Generaliti

Pentru a putea lucra cu editorul din ARITY Prolog, trebuie inserat n fiierul config.sys linia de comand DEVICE=C:DOS\ANSI.SYS /K, dac se utilizeaz MS-DOS sau Windows 3.x, respectiv linia DEVICE=C:\95\COMMAND\ANSI.SYS /K, dac se utilizeaz Windows 95.
Unele din opiunile din meniurile ARITY Prolog sunt echivalente cu combinaii de taste. n aceste cazuri, ele vor fi specificate ntre paranteze, alturi de combinaiile echivalente. Pentru a selecta un meniu se tasteaz combinaia Alt + prima liter a numelui. Selectarea unei opiuni dintr-un meniu se face tastnd litera evideniat din numele opiunii, sau folosind combinaia de taste asociat (dac exist). Atunci cnd, ntr-un meniu, dup numele unei opiuni urmeaz trei puncte, nseamn c, eventual, utilizatorul mai are de completat o cutie de dialog. Se poate naviga cu Tab sau Shift + Tab ntre cmpurile, butoanele i listele cutiei de dialog, iar n cadrul cmpurilor i listelor se poate naviga cu tastele cu sgei (, , i ). O opiune din meniu poate fi oricnd prsit tastnd Esc.

5.2

Comenzile editorului

Editorul are dou moduri de editare: inserare sau suprascriere de caractere. Trecerea de la un mod la altul se face cu tasta Ins. Deplasarea cursorului i poziionarea pe ecran se pot face cu urmtoarele taste sau combinaii de taste: Sgeat la stnga Sgeat la dreapta Sgeat n sus Sgeat n jos Ctrl + Ctrl + Un caracter la stnga. Un caracter la dreapta. O linie n sus. O linie n jos. Un cuvnt la stnga. Un cuvnt la dreapta.

Home End PageUp PageDown Ctrl + Home Ctrl + End Ctrl + g

nceputul liniei. Sfritul liniei. O pagin n sus. O pagin n jos. nceputul fiierului. Sfritul fiierului Deplasare la linia specificat.

Selectarea, copierea i cutarea n text se fac astfel: Backspace () Del Shift + , , sau Ctrl + r (Undo) Shift + Del (Cut) F2 (Copy) Shift + Ins (Paste) Del (Clear) F4 (Find) Ctrl + \ (Find Selected) F3 (Repeat Last Find) F5 (Change) terge un caracter la stnga cursorului. terge un caracter la dreapta cursorului. Selecteaz o poriune de text, ncepnd de la poziia curent a cursorului. Restaureaz o linie la starea n care era cnd cursorul a fost mutat pe ea. terge textul selectat i l mut n clipboard. copiaz n clipboard textul selectat. Copiaz textul din clipboard. terge textul selectat, fr a-l copia n clipboard. Caut un ir n text. Caut n text un ir selectat anterior. Repet ultima cutare. Caut un ir specificat i l nlocuiete cu alt ir specificat.

Mediul ARITY const dintr-o fereastr principal (main window) i zece fiiere tampon (buffers). Clipboard-ul este buffer-ul 0, n buffer-ele de la 1 ncolo fiind ncrcate fiierele cu cod Prolog. Interpretarea de ctre ARITY Prolog a coninutului unui buffer se numete consultare. Operaiile posibile cu aceste fiire (meniul Buffers) sunt:

F6 (Go to) F7 (Go to Last) Erase Buffer Reconsult Buffer Save on Exit

Reconsult on Exit

Indent Read Only

Buffer-ul activ (n care se face editarea curent) devine cel selectat. Bufer-ul anterior selectat devine activ. terge coninutul buffer-ului curent. Reconsult coninutul buffer-ului curent. Coninutul buffer-ului curent este salvat pe discul local atunci cnd buffer-ul devine inactiv (buffer-ul este nchis, sau nu mai e activ pentru c s-a trecut n alt buffer sau n fereastra principal). Coninutul buffer-ului curent este reconsultat cnd buffer-ul devine inactiv (buffer-ul este nchis, sau nu mai e activ pentru c s-a trecut n alt buffer sau n fereastra principal). Indenteaz o linie nou la fel cu linia anterioar n buffer-ul curent. Marcheaz buffer-ul curent ca putnd fi doar citit; buffer-ul nu mai poate fi modificat.
62

Tastele funcionale F1 F8 au asociate urmtoarele aciuni: F1 (Help) F2 (Copy) F3 (Repeat Last Find) F4 (Search) F5 (Change) F6 (Go to) F7 (Go to Last) F8 (Switch)
Meniul File:

Deschide fiierul cu informaii despre predicatele ARITY predefinite. Copiaz n clipboard textul selectat. Repet ultima cutare. Cutarea unui ir n text. Caut un ir specificat i l nlocuiete cu alt ir specificat. Buffer-ul activ (n care se face editarea curent) devine cel selectat. Bufer-ul anterior selectat devine activ. Comutare din buffer-ul activ n fereastra principal (main window) sau invers.

Operaiile posibile cu fiiere sunt urmtoarele: New Open File Merge File Save File Save File As Consult File Deschide un fiier nou ntr-un buffer nou. Deschide un fiier deja creat ntr-un buffer nou. Deschide un fiier i l adaug la sfritul buffer-ului curent. Salveaz buffer-ul curent ntr-un fiier. Salveaz buffer-ul curent ntr-un fiier cu un nume nou. Consult un fiier fr s l ncarce ntr-un buffer.

Operaiile posibile cu baza de cunotine ARITY Prolog sunt urmtoarele: Restore Db nchide toate fiierele deschise i apoi, pe baza informaiilor din fiierul api.idb, restaureaz baza de date a interpretorului i deschide fiierele deschise la atunci cnd s-a fcut salvarea respectiv. nchide toate fiierele deschise i apoi, pe baza informaiilor din fiierul indicat de utilizator, restaureaz baza de date a interpretorului i deschide fiierele deschise la atunci cnd s-a fcut salvarea respectiv. Salveaz baza de date a interpretorului i informaiile depre fiierele deschise n fiierul api.idb. Salveaz baza de date a interpretorului i informaiile depre fiierele deschise ntr-un fiier indicat de utilizator. Lanseaz o sesiune MS-DOS. Revenirea n mediul ARITY se face prin comanda exit. Prsire mediu de programare ARITY Prolog. Dintre fiierele deschise, modificate i nesalvate, utilizatorul este ntrebat care trebuie salvate.

Restore Db From

Save Db Save Db As Dos Shell Halt

63

5.3

Informaiile de ajutor i depanare

Mediul ARITY Prolog are urmtoarele fiiere de ajutor (help files): arity.hlp (descrierea predicatelor predefinite), debug.hlp (descrierea comenzilor de depanare a programelor), editor.hlp (descrierea comenzilor mai importante ale editorului) i errors.hlp (descrierea mesajelor de eroare generate la consultarea unui buffer sau n execuia unui predicat Prolog). Deoarece aceste fiiere sunt stocate n format ASCII, ele pot vizualizate att din mediul ARITY Prolog, ct i cu un editor simplu de texte, putnd fi listate la imprimant. Din meniul Help se poate deschide fiierul arity.hlp, selectnd opiunea Arity/Prolog, sau oricare din cele patru menionate, cu opiunea Other Topics.
Notaia argumentelor din descrierea predicatelor predefinite (din help) denot semnificaia uzual a acestora i este urmtoarea:

+ Argument -Argument ?Argument

Argumentul este, n mod normal, de intrare (trebuie specificat). Argumentul este, n mod normal, de ieire (este calculat). Argumentul poate fi de intrare sau de ieire.

S-a specificat n mod normal, deoarece, n unele cazuri, argumentele se pot folosi i altfel, folosind puterea generativ a limbajului Prolog. n exemplele descrise n seciunile urmtoare se va folosi aceeai convenie pentru indicarea argumentelor de intrare, ieire sau de ambele tipuri. Meniul Info are dou opiuni Statistics i Modify Windows. Prima ofer informaii despre starea mediului de programare (stiv, cte operaii de garbage collection care au fost fcute, memorie utilizat, etc.), iar a doua permite utilizatorului s-i creeze o interfa personalizat, stabilind proprietile ferestrelor mediului de programare (culori, domensiuni, amplasare, titlu, etc.) Meniul Debug are urmtoarele opiuni:

Spy Trace On

Se specific ce predicate trebuie urmrite la depanare Activeaz depanatorul pentru trasarea predicatelor urmrite. Este echivalent cu apelul predicatului trace. de la nceputul satisfacerii predicatului. de la terminarea satisfacerii predicatului. de la euarea satisfacerii predicatului. de la nceputul resatisfacerii predicatului.

Urmtoarele 4 opiuni specific din ce punct al evoluiei predicatelor ncep s se afieze informaii de trasare (care arat la ce structuri de date sunt legai parametrii acestora):

Call Exit Fail Redo

Trebuie menionate dou lucruri: n ARITY Prolog, un predicat (scop) este vzut ca o cutie neagr (black box) cu patru porturi, dou de intrare: call, redo i dou de ieire exit, fail. Reconsultarea unui fiier n timpul depanrii unui predicat definit n acesta duce la rezultate imprevizibile, soldndu-se de obicei cu blocarea definitiv a mediului de programare i chiar a calculatorului. De aceea, nainte de a reconsulta un fiier, este indicat s se opreasc eventuala sesiune de depanare n curs de desfurare. n timpul depanrii apare o fereastr de depanare n care sunt afiate informaiile de trasare i n care pot fi introduse urmtoarele comenzi:

Space (toggle window)

Comutare ntre fereastra depanatorului i fereastra aplicaiei.

64

Dup un exit, pentru a fora resatisfacerea (redo) pentru scopul curent. Permite apelul oricrui scop Prolog, cu rentoarcere imediat n depanator dup terminarea scopului. a (abort) Terminare forat a programului (abort) i rentoarcere la prompter-ul interpretorului. Aceast comand este indicat n locul comenzii Ctrl + c. b (break) Se intr pe un nivel de break, adic se obine un prompter de interpretor, fr a ntrerupe programul depanat. Revenirea de pe nivelul de break i continuarea depanrii se fac cu Ctrl + z. Pentru fiecare nivel nou de break interpretorul adaug nc un semn de ntrebare la prompter. De exemplu, pe nivelul al treilea, prompter-ul este ????-. c sau Enter Depanatorul trece la urmtorul port (punct de trasare). Dac portul este (creep) marcat ca urmrit n trasare, depanatorul se oprete i ateapt noi comenzi, altfel merge mai departe. d (display goal) Afieaz scopul curent de satisfcut, folosind predicatul display. e (exit interpreter) Prsire interpretor i revenire la prompter-ul DOS. f (fail goal) Foreaz euarea scopului curent. Este folositor dac utilizatorul tie deja c scopul curent va eua. h (help) Ofer informaii despre comenzile de depanare. l (leap) Depanatorul va sri la urmtorul punct de trasare. n (notrace) Dezactiveaz depanatorul. Aceast comand este echivalent apelul predicatului notrace. q (quasi-skip) Depanatorul sare la portul de exit sau fail al scopului curent. Dac ns exist un punct de trasare n scop, depanatorul se va opri la el. Acest comand se poate folosi numai dup un call sau redo pentru un scop. (Vezi i comenzile s i z.) s sau Esc (skip) Sare la portul de exit sau fail, dup cum exist sau nu puncte de trasare n cadrul scopului. Acest comand se poate folosi numai dup un call sau redo pentru un scop. (Vezi i comenzile q i z.) w (write goal) Scrie scopul curent, apelnd predicatul write. x (bach to Aceast comand poate fi folosit la un port fail sau redo. Ea foreaz choice point) depanatorul s continue euarea pn cnd este atins un port call sau exit. Dei n acest caz depanatorul nu se oprete s accepte comenzi, depanatorul afieaz cte un mesaj pentru fiecare port prin care trece. z (zip) Sare la portul de exit sau fail, dup cum alte puncte de trasare sunt puse n scopul curent. Execuia nu pstreaz informaie de depanare pentru punctele de backtracking din scop, fiind mai rapid i mai puin consumatoare de memorie. Acest comand se poate folosi numai

; (redo) @ (accept goal)

65

dup un call sau redo pentru un scop. (Vezi i comenzile q i s.)

5.4

Sintaxa ARITY Prolog

n aceast seciune se reiau elementele sintactice de baz din Prolog prezentate n prima seciune, artnd particularitile sintactice ale limbajului ARITY Prolog. Clasificarea obiectelor din ARITY Prolog este prezentat n figura 10.
atomi structuri iruri de caractere obiecte obiecte elementare numere reale variabile constante numere ntregi

Figura 10. Clasificarea obiectelor din ARITY Prolog


Un atom este un identificator care ncepe cu litera mic i se continu cu orice niruire de litere mici, litere mari, cifre sau underscore _ . Dac se dorete ca atomul s nceap cu liter mare sau s conin caractere speciale, atunci atomul se ncadreaz ntre caractere apostrof. Exemple de atomi:

dana nil x25 x_25 x_25AB x_ x__y domnul_popescu 'Ivan Bratko' '<<------>>'
Reprezentarea irurilor depinde de implementarea Prolog. n ARITY Prolog, interpretor care se aliniaz la standardul Edinbourg Prolog, irurile se reprezint ntre caractere $. Exemple de iruri de caractere:

$Constantin Noica$ $<<-------->>$


Diferena dintre iruri i atomi este urmtoarea: irurile au o reprezentare intern mai relaxat i nu pot fi folosite pe post de atomi, n timp ce atomii au de obicei reprezentri interne consumatoare de memorie n ideea c ei trebuie regsiti rapid, cutrile Prolog fcndu-se n general dup aceti atomi. Un numr ntreg este un ir de cifre zecimale, eventual precedate de un semn. Exemple de numere ntregi: 1 +23 1515 0 -97. Numerele reale depind de implementarea de Prolog. n general 66

ele sunt reprezentate ntr-o form similar celor din limbaje gen Pascal, C, etc. Exemple de numere reale: 3.14 -0.0035 100.2 +12.02 La fel ca i n Prolog standard, o variabil este un ir de litere, cifre sau underscore ( _ ) care ncepe cu liter mare sau underscore. Exemple de variabile:

X Rezultat Obiect1 Lista_de_nume _x23 _23


n Prolog exist situaii n care o variabil apare o singur dat ntr-o regul, caz n care nu avem nevoie de un nume pentru ea, deoarece nu este referit dect ntr-un singur loc. De exemplu, dac dorim s scriem o regul care ne spune dac cineva este fiul cuiva, o soluie ar fi:

este_fiu(X) :- parinte(Z, X).


Se observ c s-a denumit cu Z un printe anonim. n acest caz, nu intereseaz cine apare pe post de printe. Pentru a nu ncrca regulile cu nume inutile care pot distrage atenia i ngreuia citirea programelor, se pot considera astfel de variabile c anonime, notate cu underscore _. Deci regula anterioar se poate rescrie astfel:

este_fiu(X) :- parinte( _ , X).


Variabilele din Prolog nu sunt identice ca semnificaie cu variabilele Pascal sau C, fiind mai curnd similare cu variabilele n sens matematic. O variabil, odat ce a primit o valoare, nu mai poate fi modificat. Acest lucru elimin efectele laterale care sunt permise n limbajele procedurale. O variabil Prolog neinstaniat semnific ceva necunoscut. Pentru structurile de date care nu sunt variabile i care au nume, numele lor ncepe cu minuscul, urmat de litere, cifre sau underscore. Obiectele structurate, numite pe scurt structuri, sunt acele obiecte formate din mai muli constitueni sau componente. Componentele pot fi la rndul lor structuri. Pentru a mixa constituenii ntr-un singur obiect avem nevoie de un functor. De exemplu, dac dorim reprezentarea unui obiect structurat data, el poate fi:

data(24, august, 1997).


n acest caz, componentele sunt: 24, august i 1997, iar liantul este functorul data. Functorul poate fi orice atom. Constituenii pot fi orice obiect Prolog. S vedem un exemplu de obiect compus din alte obiecte compuse. Fie tipul punct(X, Y). Atunci tipul segment poate fi reprezentat ca segment(P1, P2). De exemplu, un segment, avnd capetele n punctele (1, 1) i (2, 3), poate fi reprezentat astfel: segment(punct(1, 1), punct(2, 3)). Exist i cazuri n care un obiect poate avea constitueni compui de adncime variabil. Astfel se ajunge la al doilea tip de recursivitate n Prolog, i anume, recursivitatea obiectelor. De exemplu, un arbore binar, cu chei numerice poate fi definit astfel:

1) Nodul vid este un arbore; putem nota acest nod vid prin constanta nil; 2) Un nod frunz este un arbore, de exemplu nod(15, nil, nil); 3) Un nod cu succesor stng i succesor drept este un arbore, de exemplu nod(20, ArbStng, ArbDrept). De exemplu, reprezentarea n Prolog a arborelui

67

1 2 4 7 5 3 6

este nod(1, nod(2, nod(4, nil, nil), nod(5, nod(7, nil, nil), nil)), nod(3, nil, nod(6, nil, nil)))
Aa cum s-a artat n prima parte, un program Prolog este o niruire de fapte (facts) i reguli (rules) care poart denumirea de clauze (clauses). Faptele i regulile au o structur comun ceea ce permite sinteza dinamic de cod care este adugat n baza de cunotine. Un fapt poate fi privit ca o regul care are ca premis (ipotez) scopul true, care este adevrat ntotdeauna. Astfel: fapt.

este echivalent cu
fapt :- true.

O regul fr antet, deci de forma:


:- scop.

determin execuia automat a scopului scop la reconsultarea bufer-ului n care apare. Efectul este similar cu cel obinut la introducerea n Main Window a ntrebrii:
?- scop. Exemple de ntrebri:

place(ellen, tennis). % lui ellen i place tenisul place(john, fotbal). % lui john i place fotbalul place(tom, baseball). % lui tom place baseball-ul place(eric, not). % lui eric i place notul place(mark, tenis). % lui mark i place tenisul place(bill, X) :- place(tom, X). % lui bill i place ce i place lui tom % Ce sporturi i plac lui john? ?- place(john, X). X = fotbal % Cui i place tenisul? ?- place(Y, tenis). Y = ellen; Y = mark % Putem pune i ntrebri particulare: ?- place(ellen, tenis).
68

yes ?- place(ellen, fotbal). no ?- place(bill, baseball). yes


Urmeaz acum o serie de exemple necomentate pe care cititorul este ndemnat s le urmreasc i s le neleag.

Ex1.

Exprimati in Prolog urmatoarele fapte: 1) susan are un cal; 2) rex mnnc carne; 3) aurul este preios; 4) maina este un mercedes albastru cu capacitatea de cinci cltori.

Rspunsuri: 1) are(susan, cal). 2) mananca(rex, carne). 3) pretios (aur). 4) masina(mercedes, albastru, 5). Ex2. Fie faptele: masina(mercedes, albastru, 5). masina(chrysler, rosu, 4). masina(ford, gri, 8). masina(datsun, rosu, 5). ntrebrile: 1) Ce tipuri de maini au cinci locuri pentru cltori? 2) Ce maini sunt roii? se scriu: ?- masina(Tip, _ , 5). ?- masina(Tip, rosu, _ ). iar regula: X este o main mare dac poate transporta cel puin 5 cltori. se scrie: masina_mare(X) :- masina(X, _ , Nr_calatori), Nr_calatori >= 5.

69

Ex3. Doi copii pot juca un meci ntr-un turneu de tenis dac au aceeai vrst. Fie urmtorii copii i vrstele lor: copil(peter, 9). copil(paul, 10). copil(chris, 9). copil(susan, 9). Toate perechile de copii care pot juca un meci ntr-un turneu de tenis sunt calculate de predicatul: pot_juca(Pers1, Pers2) :copil(Pers1, Varsta), copil(Pers2, Varsta), Pers1 \= Pers2. Ex4. S scriem un program care s i gseasca Anei un partener la bal. Ea dorete sa mearga cu un brbat care este nefumtor sau vegetarian. Pentru aceasta dispunem de o baz de date cu informaii despre civa brbaii: barbat(gelu). barbat(bogdan). barbat(toma). fumator(toma). fumator(dan). vegetarian(gelu). Pentru a exprima doleanele Anei, vom scrie dou reguli: 1) Ana se ntlnete cu X dac X este brbat i nu este fumtor. 2) Ana se ntlnete cu X dac X este brbat i este vegetarian. Adic: ana_se_intalneste_cu(X) :- barbat(X), not(fumator(X)). ana_se_intalneste_cu(X) :- barbat(X), vegetarian(X).

5.5
EP1.

Exerciii propuse
Se d o baz de fapte de forma: parinte(Parinte, Copil). barbat(Persoana). femeie(Persoana).

Se cere: 1) S se introduc cteva fapte de aceast form. 2) S se scrie regulile care definesc urmtoarele relaii de rudenie: tata(Tata, Copil). mama(Mama, Copil). fiu(Parinte, Copil). fiica(Parinte, Copil). bunic(Bunic, Copil).
70

bunica(Bunica, Copil). nepot(Bunic, Copil). nepoata(Bunic, Copil). 3) S se puna urmtoarele ntrebri: Cine este printele lui dan? Cine este fiu? Cine este bunic? Cine este fiu i tat? EP2. S se descrie principalele operaii logice n Prolog: not, or, and, xor, nor, nand. Pentru aceasta se consider faptele op_not(Variabila, Rezultat) i op_or(Variabila1,Variabila2, Rezultat). S se scrie: 1) faptele necesare descrierii lui op_not i op_or; 2) regulile necesare construciei celorlali operatori pe baza lui op_not i op_or. Se cer reguli pentru: op_and(Var1, Var2, Rezultat). op_xor(Var1, Var2, Rezultat). op_nor(Var1, Var2, Rezultat). op_nand(Var1, Var2, Rezultat). EP3. Se dau urmtoarele enunuri: 1) Oricine poate citi este literat. 2) Delfinii nu sunt literai. 3) Anumii delfini sunt inteligeni. S se demonstreze n Prolog c: Exist fiine inteligente care nu pot citi. EP4. Se dau urmtoarele enunuri: 1) 2) 3) 4) 5) 6) 7) 8) Fiecare om i este devotat cuiva. Oamenii ncearc s asasineze dictatorii fa de care nu sunt devotai. Toi pompeienii erau romani. Cezar era dictator. Fiecare roman fie l ura pe Cezar, fie i era devotat. Marcus era pompeian. Marcus era om. Marcus a ncercat s l asasineze pe Cezar.
71

S se demonstreze n Prolog c: 1) Marcus nu i era devotat lui Cezar. 2) Marcus l ura pe Cezar.

Recursivitate n Prolog

Multe definiii de concepte sunt definiii recursive, aa cum este de exemplu definiia recursiv a noiunii de list, arbore, sau cum a fost definiia recursiv a predicatului stramo prezentat n seciunea 4.1. O serie de noiuni matematice, cum ar fi factorialul unui numr sau numerele lui Fibonacci au, de asemenea, o definiie recursiv.

6.1

Relaii recursive

Limbajul Prolog permite exprimarea definiiilor recursive prin definiia recursiv a predicatelor. Intr-o astfel de definiie trebuie ntotdeauna s se defineasc un fapt sau o regul care marcheaz punctul de oprire din recursivitate, deci cazul particular al definiiei recursive. Aa cum s-a artat n seciunea 4.4, trebuie analizate i aspecte privind direcia de construire a soluiei. Ex1. S se scrie un program care calculeaz n!, unde n! = 1 * 2 * 3 *...* n. % fact(+N, - NFact) fact(0, 1). fact(N,NFact) :N1 is N - 1, fact(N1, Rezult), NFact is N * Rezult. % Factorialul lui 0 este 1, punctul de oprire. % Factorialul lui N este NFact dac: % calculm (N - 1) pentru al putea pasa ca argument % fie Rezult factorial de (N - 1) % atunci NFact este N nmulit cu factorial de (N - 1)

Ex2. S se scrie un program care calculeaz termenul n din irul lui Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, ..., n care f(n) = f(n - 1) + f(n - 2), pentru n > 2. % fib(+N, -F) fibo(1, 1). fibo(2, 1). fibo(N, F) :- N > 2, N1 is N - 1, N2 is N - 2, fibo(N1, F1), fibo(N2, F2), F is F1 + F2. Aceast implementare este ineficient deoarece n calculul lui fibo(N, _ ), fibo(N-k, _ ) este apelat de k ori. Implementarea urmtoare elimin aceast deficien, deoarece predicatul fib, care calculeaz F = f(M), este apelat cu ultimii doi termeni F1 = f(M-2) i F2 = f(M-1), pentru M = 2N: fibo(N, F) :- fib(2, N, 1, 1, F). % predicat auxiliar fib(+M, +N, +F1, +F2, -F)
72

fib(M, N, _ , F2, F2) :- M >= N. fib(M, N, F1, F2, F) :M < N, M1 is M + 1, F1plusF2 is F1 + F2, fib(M1, N, F2, F1plusF2, F). Ex3. S se scrie un predicat care calculeaz cel mai mare divizor comun pentru dou numere. Se folosete definiia recursiv a lui Euclid: Fie a i b dou numere ntregi pozitive. dac b = 0 atunci cmmdc(a, b) = a; altfel cmmdc(a, b) = cmmdc(b, r), unde r este restul mpririi lui a la b. Implementarea n Prolog este: % cmmdc(+A, +B, -Cmmdc) cmmdc(A, 0, A). cmmdc(A, B, Rez) :- B > 0, Rest is A mod B, cmmdc(B, Rest, Rez). Ex4. S scriem predicatul divizor care testeaz dac un numr este divizibil cu altul. Pe baza acestui predicat s se scrie predicatul prim care verific dac un numr este prim. % divizor(+A,+B) % prin(+N) divizor(A, B) :- 0 is B mod A. prim(N):- Div is N - 1, nu_are_div(N, Div). nu_are_div(N, 1). nu_are_div(N, Divizor) :not(divizor(Divizor, N)), DivNou is Divizor - 1, nu_are_div(N, DivNou). Ex5. Predicatul rezolv(N) rezolv problema turnurilor din Hanoi cu N discuri. % rezolva(+N) rezolva (N) :- hanoi(N, stnga, dreapta, mijloc). hanoi(0, _ , _ , _ ). hanoi(N, A, B, C) :N > 0, N1 is N - 1, hanoi(N1, A, C, B), write($Din $), write(A), write($ pe $), write(B), nl, hanoi(N1, C, B, A).

6.2

Problema trezorierului

In aceast seciune i n seciunile urmtoare ale capitolului se vor prezenta rezolvrile n Prolog ale cteva probleme clasice ce implic recursivitate. Fiecare problem este prezentat sub urmtoarea form:
73

1) Enunul problemei (ipoteze i concluzie); 2) Rescrierea enunului sub form de clauze Prolog; 3) Demonstraia concluziei prin formularea unei interogri corecte n Prolog. Ipoteze: 1) Nici un membru al clubului nu are datorii la trezorierul clubului. 2) Dac un membru al clubului nu a pltit taxa, atunci el are datorii la trezorierul clubului. 3) Trezorierul clubului este un membru al clubului. Concluzie: Trezorierul clubului a pltit taxa. % Ipoteza 1 fara_datorii(X) :- membru(X). % Ipoteza 2 platit_taxa(X) :- fara_datorii(X). % Ipoteza 3 membru(trezorier). % Concluzia % ?- platit_taxa(trezorier).

6.3

Problema parteneriatului

Ipoteze: 1) Tom este corect. 2) Bill nu este partener cu Tom. 3) Dac dou persoane X i Y sunt corecte, atunci X este partener cu Y. 4) Dac Bill nu este corect, atunci John este corect. 5) Dac o persoan X este partener cu Y, atunci i Y este partener cu X. Concluzie: John este partener cu Tom. % Ipoteza 1 corect(tom). % Ipoteza 2 not_partener(bill, tom). % Ipoteza 3 partener(X, Y) :- corect(X), corect(Y), X \= Y. % Ipoteza 4, se folosete i ipoteza 3, inversat conform principiului reducerii % la absurd: formula p q este echivalent cu !q !p. corect(john) :- not_corect(bill).
74

not_corect(X) :- not_ambii_coreci(X, Y), corect(Y). % din ipoteza 3 not_ambii_coreci(X, Y) :- not_partener(X, Y). % Ipoteza 5 % Este reprezentat n definiia lui partener (ipoteza 3), care include simetria. % Concluzia % ?- partener(john, tom).

6.4

Problema vecinilor

Ipoteze: 1) tefan este vecin cu Petre. 2) tefan este cstorit cu o doctori care lucreaz la Spitalul de Urgen. 3) Petre este cstorit cu o actri care lucreaza la Teatrul Naional. 4) tefan este meloman i Petre este vntor. 5) Toi melomanii sunt sentimentali. 6) Toi vntorii sunt mincinoi. 7) Actriele iubesc brbaii sentimentali. 8) Soii au aceiai vecini. 9) Cstoria i vecintatea sunt relaii simetrice. Concluzie: l iubete soia lui Petre pe tefan? % Ipoteza 1 vecin1(stefan, petre). % Ipoteza 2 cstorit1(stefan, sotie_stefan). doctorita(sotie_stefan). lucreaza(sotie_stefan,urgenta). % Ipoteza 3 casatorit1(petre, sotie_petre). actrita(sotie_petre). lucreaza(sotie_petre, national). % Ipoteza 4 meloman(stefan). vanator(petre). % Ipoteza 5 sentimental(X) :- meloman(X). % Ipoteza 6 mincinos(X) :- vanator(X). % Ipoteza 7 iubeste(X, Y) :- actrita(X), sentimental(Y).
75

% Ipoteza 8 vecin(X, Y) :- casatorit(X, Z), vecin(Z, Y). % Ipoteza 9 vecin(X, Y) :- vecin1(X, Y). vecin(X, Y) :- vecin1(Y, X). casatorit(X, Y) :- casatorit1(X, Y). casatorit(X, Y) :- casatorit1(Y, X). % Concluzia concluzie :- casatorit(petre, Sotie), iubeste(Sotie, stefan). % ?- concluzie.

6.5

Problema unicornului

Problema unicornului este o problem celebr formulat de Lewis Carroll n cartea sa "Alice n ara minunilor". Ipoteze: 1) Leul minte luni, mari i miercuri i spune adevrul n toate celelalte zile. 2) Unicornul minte joi, vineri i smbt i spune adevrul n toate celelalte zile. 3) Astzi Leul spune: "Ieri a fost una din zilele n care eu mint." 4) Tot astzi Unicornul spune: "Ieri a fost una din zilele n care eu mint." Concluzie: Ce zi este astzi? (Rspuns: joi). Prima variant: % zilele sptmnii urmeaza(luni, marti). urmeaza(marti, miercuri). urmeaza(miercuri, joi). urmeaza(joi, vineri). urmeaza(vineri, sambata). urmeaza(sambata,duminica). urmeaza(duminica, luni). % Ipoteza 1 % minte(Animal, Zi) - Animal minte n ziua Zi. minte(leu, luni). minte(leu, marti). minte(leu, miercuri). % Ipoteza 2 minte(unicorn, joi). minte(unicorn, vineri). minte(unicorn, sambata). % Ipotezele 3 i 4 % spune(Animal, Zi) - Animal spune adevrul n ziua Zi. spune(Animal, Azi) :- urmeaza(Ieri, Azi), minte(Animal, Ieri). posibil(Animal, Azi) :- spune(Animal, Azi), not(minte(Animal, Azi)). posibil(Animal, Azi) :- minte(Animal, Azi), not(spune(Animal, Azi)). % Pregtire concluzie azi(Azi) :- posibil(leu, Azi), posibil(unicorn, Azi). % Concluzia
76

% ?- azi(Azi). A doua variant: % zilele sptmnii urmeaza(luni, marti). urmeaza(marti, miercuri). urmeaza(miercuri, joi). urmeaza(joi, vineri). urmeaza(vineri, sambata). urmeaza(sambata,duminica). urmeaza(duminica, luni). % Ipoteza 1 % spune(Animal, Zi, Ce) - Ce spune Animal n fiecare Zi. spune(leu, luni, minciuna). spune(leu, marti, minciuna). spune(leu, miercuri, minciuna). spune(leu, joi, adevar). spune(leu,vineri, adevar). spune(leu, sambata, adevar). spune(leu, duminica, adevar). % Ipoteza 2 spune(unicorn, luni, adevar). spune(unicorn, marti, adevar). spune(unicorn, miercuri, adevar). spune(unicorn, joi, minciuna). spune(unicorn, vineri, minciuna). spune(unicorn, sambata, minciuna). spune(unicorn, duminica, adevar). % Ipotezele 3 i 4 enunt(Animal, Azi) :- urmeaza(Ieri, Azi), spune(Animal, Ieri, minciuna). % adevarul - Ce nseamna c minte, pentru Prolog; este un metapredicat. adevarul(Animal, Zi, Enunt, not(Enunt)) :- spune(Animal, Zi, minciuna). adevarul(Animal, Zi, Enunt, Enunt) :- spune(Animal, Zi, adevar). % Pregtire concluzie azi(Azi) :adevarul(leu, Azi, enunt(leu, Azi), Adevar1), Adevar1, % sau call(Adevar1) adevarul(unicorn, Azi, enunt(unicorn, Azi), Adevar2), Adevar2. % sau call(Adevar2) % Concluzia % ?- azi(Azi).

6.6

Exerciii propuse

S se rescriere enunurile urmtoarelor probleme sub form de clauze Prolog i s se demonstreze concluziile prin formularea unor interogri corecte n Prolog. EP1. Ipoteze 1. Oricine poate citi este literat. 2. Delfinii nu sunt literai. 3. Anumii delfini sunt inteligeni. Concluzie
77

Exist fiine inteligente care nu pot citi. EP2. Ipoteze 1. Dac oraul X este legat de oraul Y prin drumul D i pot circula biciclete pe drumul D, atunci se poate merge de la X la Y. 2. Dac oraul X este legat de oraul Y prin drumul D, atunci i oraul Y este legat de oraul X prin drumul D. 3. Dac se poate merge de la X la Y i de la Y la Z, atunci se poate merge de la X la Z. 4. Oraul a este legat de oraul b prin drumul d1. 5. Oraul b este legat de oraul c prin drumul d2. 6. Oraul a este legat de oraul c prin drumul d3. 7. Pe drumul d1 pot circula biciclete. 8. Pe drumul d2 pot circula biciclete. Concluzie Se poate merge de la oraul a la oraul c.

EP3. Se nlocuiete ipoteza 8 de la EP2 cu Pot circula biciclete fie pe drumul d1, fie pe drumul d3, dar nu pe ambele n acelai timp. S se demonstreze aceeai concluzie. EP4. Ipoteze 1. Marcus era om. 2. Marcus era pompeian. 3. Toi pompeienii erau romani. 4. Cezar era dictator. 5. Fiecare roman i era devotat lui Cezar sau l ura. 6. Fiecare om i este devotat cuiva. 7. Oamenii ncearc s i asasineze pe dictatorii fa de care nu sunt devotai. 8. Marcus a ncercat s l asasineze pe Cezar. Concluzii 1. Marcus nu i era devotat lui Cezar. 2. Marcus l ura pe Cezar.

Prelucrarea listelor n Prolog

Structura de date list este cea mai frecvent utilizat structur de date n programele Prolog. Acest capitol prezint n detaliu lucrul cu liste n Prolog.

78

7.1

Predicate de prelucrare a listelor

Cele mai frecvente predicate utilizate n prelucrarea listelor sunt cel de apartenen a unui element la o list i concatenarea a dou liste, care au fost prezentate n prima parte a lucrrii. Reamintim aceste definiii: member(Elem, [Elem|_]) :- !. member(Elem, [_|Rest]) :- member(Elem, Rest). append([], L2, L2). append([Prim1|Rest1], Lista2, [Prim1|Rest3]) :- append(Rest1, Lista2, Rest3). In continuare se prezint o serie de alte predicate utile n prelucrarea listelor.
Eliminarea unui obiect dintr-o list. S scriem un predicat care elimin un obiect dintr-o list. Astfel, elim(a, [a, b, c], L) va returna n L lista [b, c]. Implementarea n Prolog este:

% elim(+El,+Lista,-ListaRez) elim(X, [X | Rest], Rest). elim(X, [Y | Rest], [Y | Rest1]) :- elim(X, Rest, Rest1).
Conform acestei implementri, elim nu va elimina dect o apariie a elementului cutat. Astfel, eliminarea lui a din lista [a,b,a,c] va genera dou soluii posibile:

?- elim(a, [a, b, a, c], L). L = [b, a, c]; L = [a, b, c]; no Dar este posibil i ntrebarea Ce liste din care se elimina a dau ca rezultat lista [b, c]?: ?- elim(a, L, [b, c]). L = [a, b, c]; L = [b, a, c]; L = [b, c, a]; no
Incluziunea listelor. Fie un predicat care este adevrat dac o list este sublista alteia. De exemplu, sublist([c, d, e], [a, b, c, d, e, f]) este adevrat, iar sublist([b, c, e], [a, b, c, d, e, f]) este fals. Ne putem folosi de predicatul deja scris append. O list S este sublist a listei L dac:

1) Exist o descompunere a lui L n L1 i L2 i 2) Exist o descompunere a lui L2 n S si L3. Implementare: % sublist(+SubLista,+Lista) sublist(S, L) :- append(L1, L2, L), append(S L3, L2).

79

Aceast implementare are un mic defect: afieaz lista vid de mai multe ori. ncercai s aflai de ce. O variant care elimin acest defect este subset: subset([], L). subset([X | Rest], L) :- member(X, L), subset(Rest, L).
Liniarizarea listelor. Vom scrie predicatul liniar(ListaListe, Lista), unde ListaListe este o list de elemente care pot fi rndul lor liste, iar n Lista se construiete liniarizarea listei ListaListe:

% liniar(+Lista,ListaLiniarizata) liniar([] , []). liniar([[ ] | Rest], Rez) :- liniar(Rest, Rez). liniar([X | Rest], [X | Rez]) :- X \= [], X \= [ _ | _ ], liniar(Rest, Rez). liniar([[X | Rest] | RestList], Rez) :- liniar([X, Rest | RestList], Rez). Un exemplu de execuie este: ?- liniar([1, 2, [3, 4], [5, [6, 7], [[8], 9]]], L). L = [1, 2, 3, 4, 5, 6, 7, 8, 9]. yes
Predicatul descomp(N, Lista) primete un numr ntreg N i ntoarce o lista factorilor primi ai numrului N; de exemplu: descomp(12, [2, 2, 3]) este adevrat.

% descomp(+N,?L) descomp(N, L) :- factp(N, L, 2). factp(1, [ ], _ ). factp(N, [Divizor | Lista], Divizor) :N > 1, 0 is N mod Divizor, N1 is N // Divizor, factp(N1, Lista, Divizor). factp(N,Lista,Divizor) :N > 1, not(0 is N mod Divizor), D1 is Divizor + 1, factp(N, Lista, D1).
Predicatul palindrom(Lista) verific dac o list este palindrom. Un palindrom este o secven care, dac este parcurs de la stnga la dreapta sau de la dreapta la stnga, este identic; de exemplu: [a, b, c, b, a] sau [a, b, c, c, b, a].

% Idee: o list este palindrom dac este egal cu inversa ei. palindrom(L) :- reverse(L, [], L). reverse([], Acc, Acc). reverse([X | Rest], Acc, L) :- reverse(Rest, [X | Acc], L).

7.2

Mulimi

Mulimile pot fi reprezentate n Prolog ca liste. Predicatul multime(L, M) transform lista L n mulimea M. multime([], []). multime([X | Rest], Rez) :- member(X, Rest), mulime(Rest, Rez). multime([X | Rest], [X | Rez]) :- not(member(X, Rest)), multime(Rest, Rez).
80

Predicatul de definire a interseciei a dou liste prezentat n seciunea 4.4 se poate aplica i pentru obinerea interseciei a dou mulimi. Prezentm n continuare predicatul de determinare a reuniunii a dou mulimi.

% reun(+L1,+L2,-L) reun([],L,L). reun([X | Rest], L, Rez) :-member(X,L), reun(Rest,L,Rez). reun([X | Rest], L, [X | Rez]) :-not member(X,L), reun(Rest,L,Rez).

7.3

Problema drumurilor
drum(bucuresti, ploiesti). drum(bucuresti, cheia). drum(cheia, brasov). drum(brasov, bucuresti). drum(cheia, sinaia). drum(ploiesti, sinaia). drum(ploiesti, brasov).

Fie o baz de date cu drumuri ntre orae, de forma drum(oras1, oras2):

Predicatul traseu(X, Y, T) este adevrat dac se poate ajunge de la oraul X la oraul Y, calculnd si traseul T ntre cele dou orae. Drumurile sunt bidirecional (dac exista un drum de la X la Y, atunci exist implicit un drum de la Y la X).

member(X, [Y | T]) :- X == Y, ! ; member(X, T). traseu(Y, X) :- traseu(X, Y, [X]). traseu(Y, Y, Traseu) :- write(Traseu), nl. traseu(X, Y, Traseu) :(drum(X, Z) ; drum(Z, X)), not member(Z, Traseu), traseu(Z, Y, [Z | Traseu]). traseu( _ , _ , _ ) :- write($Nu exista traseu.$), nl. % cateva teste test :traseu(bucuresti, sinaia), traseu(sinaia, bucuresti), traseu(bucuresti, ploiesti), traseu(ploiesti, bucuresti), traseu(cheia, craiova). Dac apelm predicatul test sistemul va rspunde: ?- test. [bucuresti, brasov, cheia, sinaia] [sinaia, ploiesti, bucuresti] [bucuresti, brasov, cheia, sinaia, ploiesti] [ploiesti, bucuresti] Nu exista traseu.
81

yes

7.4
EP1.

Exerciii propuse
Aflai care este defectul predicatului sublist.

EP2. Folosind predicatul elim, punei ntrebarea: Ce elemente se pot elimina din [a, b, a, c] i ce list rezult n cazul fiecrei eliminri? EP3. S se defineasc i s se exemplifice cu cte dou exemple n Prolog urmtoarele predicate de prelucrare a listelor: 1) invers(Lista, ListaInversata) - inverseaz elementele unei liste; s se scrie dou variante ale predicatului de inversare a unei liste: o variant n care lista inversat este calculat pe ramura de revenire din recursivitate i o variant n care lista inversat este calculat pe ramura de avans n recursivitate. 2) reun(Lista1, Lista2, ListaRez) - produce ListaRez care conine reuniunea elementelor din Lista1 i din Lista2; se va da o implementere alternativ pe baza predicatului multime din seciunea 7.2. 3) rotire(Lista, Directie, Nr, ListaRez) - rotete Lista cu un numr de Nr elemente la stnga (dac Directie = stg) sau la dreapta (dac Directie = dr), depunnd rezultatul n ListaRez; EP4. S se scrie predicatul Prolog substitutie(X, Y, L1, L2), unde L2 este rezultatul substituirii tuturor apariiilor lui X din lista L1 cu Y, producnd lista L2. Ex: substitutie(a, x, [a, [b,a,] c], L2) va produce: L2 = [x, [b, x], c]. EP5. S se scrie predicatul imparte(L, L1, L2) care mparte lista L n dou subliste L1 i L2, care au un numr de elemente aproximativ egal, fr a calcula lungimea listei L. Ex: imparte([a, b, c, d, e], L1, L2) va produce: L2 = [a, b, c] i L3 = [d, e].

8
8.1

Mecanisme specifice Prolog


Exemple de utilizare a mecanismului cut

Reamintim funcionarea mecanismului cut: s denumim scop printe scopul care identific cu antetul clauzei ce conine cut. Cnd cut este ntlnit ca scop, el reuete imediat, dar oblig sistemul s rmn la toate opiunile fcute ntre momentul apelului scopului printe i momentul ntlnirii cut-lui. Toate alternativele care rmn ntre scopului printe i cut sunt ignorate. S investigm aceast funcionare pentru urmtorul exemplu:

C :- P, Q, R, !, S, T, U. C :- V. A :- B, C, D.

82

?- A.
Execuia scopului C va fi afectat de cut astfel: procesul de backtracking va fi posibil pe lista de scopuri P, Q, R; totui, ndat ce cut-ul este atins, toate soluiile alternative ale listei de scopuri P, Q, R sunt eliminate. Clauza alternativ despre C:

C :- V. va fi i ea ignorat. Procesul de acktracking va fi totui nc posibil pe lista de scopuri S, T, U. Scopul printe al clauzei care conine cut-ul este scopul C din clauza: A :- B, C, D. Prin urmare cut-ul va afecta doar execuia scopului C. Pe de alt parte, el nu va fi vizibil n scopul A. Astfel, procesul de backtracking n cadrul listei de scopuri B, C, D va rmne activ indiferent de cut-ul din clauza folosit pentru a satisface C.
Am definit predicatul care calculeaz maximul dintre dou numere (n prima parte) astfel:

% max(+X, +Y, -Max). max(X, Y, X) :- X >= Y. max(X, Y, Y) :- X < Y. Cele dou reguli sunt mutual exclusive. Dac prima reuete atunci a doua va eua. Dac prima eueaz, atunci a doua trebuie s reueasc. Prin urmare, o reformulare mai eficient este posibil: dac X Y atunci Max = X altfel Max = Y. Aceasta se scrie n Prolog utiliznd cut astfel: max(X, Y, X) :- X >= Y, !. max( _ , Y, Y).
S vedem ce se ntmpl ns atunci cnd dorim s folosind puterea generativ a limbajului Prolog:

% max(?X, ?Y, ?Max) max(X, Y, X) :- X >= Y. max(X, Y, Y) :- X < Y. ?- max(X, 3, 4). ?- max(X, 3, 3). ?- max(3, X, 4). ?- max(X, X, 3). X=4 X=3 X=4 X=3

% max(?X, ?Y, ?Max) max(X, Y, X) :- X >= Y, !. max( _ , Y, Y). X=4 X=3 X=4 X=3

Cazuri n care predicatul max trebuie s eueze: ?- max(X, 3, 2). ?- max(3, X, 3). ?- max(3, X, 2). No No No
83

no X=3 X=2

?- max(X, Y, 3). ?- max(X, 3, X). ?- max(3, X, Y). ?- max(X, 3, Y).

No no no no

X = _0038 Y = 3 X=3 X = _0038 Y = _0038 X = _0038 Y = 3

Din exemplele prezentate se observ c varianta mai explicit se comport corect n toate cazurile, n timp ce varianta eficient genereaz rezultate eronate. De aceea, nainte de a utiliza cut trebuie s ne gndim n ce context i cum vor fi apelate predicatele n care el apare, deoarece adesea folosirea lui cut crete posibilitatea apariiei erorilor de programare.

8.2

Negaia ca insucces

Faptul c ceva nu este adevrat n Prolog poate fi exprimat explicit n Prolog utiliznd predicatul special fail, care eueaz ntotdeauna, fornd scopul printe s eueze. Enunul Maria iubete toate animalele cu excepia erpilor se exprim n Prolog astfel:

iubete(maria, X) :- arpe(X), !, fail. iubete(maria, X) :- animal(X).


Prima regul va avea grij de erpi: dac X este arpe atunci cut-ul va mpiedica backtracking-ul (excluznd astfel a doua regul) i predicatul fail va cauza euarea. Cele dou clauze pot fi compactate ntr-una singur astfel:

iubete(maria, X) :- arpe(X), !, fail ; animal(X).


La fel se poate scrie un predicat care testeaz dac dou numere sunt diferite:

diferite(X, X) :- !, fail. diferite( _ , _ ). sau: diferite(X, Y) :- X = Y, !, fail ; true.


Predicatul not este predefinit n ARITY Prolog ca un operator prefixat, astfel nct not(arpe(X)) se poate scrie ca not arpe(X). Exemplele anterioare se pot rescrie cu not astfel:

iubete(maria, X) :- animal(X), not(arpe(X)). diferite(X, Y) :- not ( X = Y ).


Predicatul not funcioneaz deoarece raionamentul Prolog se bazeaz pe ipoteza lumii nchise. Not-ul definit prin euare nu corespunde exact cu negaia din logica matematic. De aceea folosirea lui not trebuie fcut cu atenie. In ARITY Prolog, mai exist un cut parial, numit snip [! !], care specific o list de scopuri care sunt ignorate n backtracking. Fie scopul p definit prin regulile:

p :- a, b, [! c, d, e !], f, g. p :- t, u, v.
Dac scopurile a, b, c, d i e reuesc scopul printe al lui p rmne fixat pe prima regul a lui p i prin backtracking se pot resatisface scopurile a, b, f i g. De asemenea, att timp ct nu s-au satisfcut toate scopurile din cadrul snip-ului, pn la satisfacerea scopului e se pot resatisface prin backtracking scopurile a, b, c i d. Dac nu se poate satisface deloc f, dup execuia tuturor satisfacerilor scopurilor a, b, c i d, se trece la regula a doua a predicatului p. Ca o aplicaie la cele discutate pn acum, iat trei exemple:

Ex1:

Plasarea a opt regine pe o tabl de ah astfel nct ele s nu se atace ntre ele:
84

% solutie(?Solutie) solutie([]). solutie([X/Y | CelelalteRegine]) :solutie(CelelalteRegine), member(Y, [1, 2, 3, 4, 5, 6, 7, 8]), not ataca(X/Y, CelelalteRegine). % ataca(+Regina, +CelelalteRegine) - verific dac Regina atac CelelalteRegine ataca(X/Y, CelelalteRegine) :member(X1/Y1, CelelalteRegine), (Y1 = Y; Y1 is Y + X1 - X; Y1 is Y - X1 + X). % member(?X, ?Lista) - member generativ member(X, [X| _ ]). member(X, [ _ |L]) :- member(X, L). % model de soluie model([1/Y1, 2/Y2, 3/Y3, 4/Y4, 5/Y5, 6/Y6, 7/Y7, 8/Y8]). % toate soluiile toate :- model(L), assert(count(1)), !, ( soluie(L), retract(count(N)), N1 is N + 1, assert(count(N1)), write(N), write($.$), [! N < 10, tab(3) ; tab(2) !], write(L), nl, fail ; retract(count( _ )), true).
Cut-ul a fost introdus doar pentru eficien (cut verde), iar parantezele care nchid disjuncia de conjuncii de scopuri au fost puse din cauza lui. Clauzele count(NumrSoluie) au fost folosite pentru numerotarea soluiilor, iar snip-ul pentru indentarea lor. Predicatul predefinit tab(N) afieaz N blank-uri.

Ex2:

Fie urmtoarele predicate: a(X) :- (X is 1; X is 2), tab(1), write(a(X)). b(X) :- X = 1, write($ !b$), !, fail ; write($ b$). c(X) :- X > 1, write($ !c$), !, fail ; write($ c$). p :p :p :p :nl, write([p1]), a(X), [! b(X), fail !], c(X). nl, write([p2]), a(X), [! b(X) !], c(X). nl, write([p3]), a(X), [! b(X) !], not c(X). nl, write([p4]).

La interogarea: ?- p.
85

sistemul va rspunde: [p1] a(1) !b a(2) b [p2] a(1) !b a(2) b !c [p3] a(1) !b a(2) b !c yes Ex3. S scriem predicatul unific(X, Y) care reuete dac X i Y unific, fr ns a modifica pe X sau pe Y. Folosind predicatul not, putem scrie: unifica(X, Y) :- not not (X = Y). Dac X i Y unific, adic X = Y reuete, atunci not (X = Y) eueaz, unificarea fiind desfcut, i not not (X = Y) reuete (deci unific(X, Y) reuete), X i Y rmnnd neunificate.

8.3

Utilizarea operatorilor

Notaia cu operatori permite programatorului s ajusteze sintaxa programelor conform propriilor dorine. nelegerea programelor este mult mbuntit atunci cnd se folosesc operatori.
Exemplu: Dac definim operatorii joaca i si:

:- op(300,xfx, joaca). :- op(200, xfy, si). atunci urmtorii termeni sunt obiecte legale din punct de veder sintactic: Termen1 = tom joaca fotbal si tenis. Termen2 = susan joaca tenis si volei si ping-pong.
Calculele aritmetice sunt fcute prin proceduri predefinite. Evaluarea unei expresii aritmetice este forat de procedura is i de predicatele de comparaie <, =<, etc. De exemplu:

?- X = 3 / 2 X=3/2 ?- X is 3 / 2 X = 1.5
n continuare este prezentat ca exemplu un mini-sistem de reguli de producie:

% operatorii fundamentali :- op(100, yfx, si). :- op(110, yfx, sau). :- op(120, fx, daca). :- op(130, xfy, atunci). % intreb(+Fapt) - deduce valoare de adevar unui fapt X si Y :- intreb(X), intreb(Y).
86

X sau Y :- intreb(X) ; intreb(Y). intreb(X) :- cunosc(X). intreb(X) :- call(X). intreb(X) :- daca Y atunci X, [! intreb(Y) !], adaug(X). intreb(X) :- intreb_utiliz(X). % adaug(+Fapt) - adauga un fapt cunoscut ca fiind adevarat in memoria % sistemului sub forma cunosc(Fapt). adaug(X) :- not var(X), (cunosc(X) ; asserta(cunosc(X))), !. % intreb_utiliz(F) - intreaba utilizatorul despre valoarea de adevar a unui fapt % despre care nu se poate deduce nimic intreb_utiliz(X) :- X =.. [Op, Y], var(Y), !, fail. intreb_utiliz(X) :- X =.. [Op, Y, Z], (var(Y) ; var(Z)), !, fail. intreb_utiliz(X) :- X =.. [Op, _ , _ , _ | _ ], !, fail. intreb_utiliz(X) :not(var(X)), write(X), write('? [y/_ ] '), (read(y), adaug(X) ; fail ), !. % ret - elimina toate faptele din memoria sistemului ret(X) :- retract(cunosc(X)), fail ; true. ret :nl, write('fapte retractate:'), nl, retract(cunosc(X)), tab(2), write(cunosc(X)), nl, fail ; true. % operatorii utilizatorului :- op(10, xf, zboara). :- op(20, xfx, are). :- op(20, xfx, este). % faptele cunoscute de utilizator (fapte Prolog). coco are cioc. % echivalent cu: are(coco, cioc) coco are pene. coco are picioare. % regulile utilizatorului pentru generarea de noi fapte % (aceste reguli sunt tot fapte Prolog) daca X are picioare atunci X este fiinta. % echivalenta cu: atunci(daca(are(X, picioare)), este(X, fiinta)). daca X este fiinta si X are pene atunci X zboara.
87

daca X este fiinta si X are pene atunci X are cioc. daca X zboara si X are cioc atunci X este pasare. daca X este pasare si X este fiinta atunci X este frumos. daca X este frumos sau X este pete atunci X este vnat. daca X are solzi atunci X este pete. daca X este vnat atunci X este nefericit. % posibile intrebari ale utilizatorului a :- intreb(coco este pete), ret. b :- intreb(coco este X), write(coco este X), nl, fail ; ret. c :- intreb(coco are X), write(coco are X), nl, fail ; ret. d :- intreb(Z), write(Z), nl, fail ; ret. e :- intreb(X este Y), write(X este Y), nl, fail ; ret.
Predicatul ret a fost apelat doar pentru a porni de fiecare dat cu memoria vid. Altfel, pe msur ce rspunde ntrebrilor utilizatorul, memoria sistemului crete (sistemul nva).

8.4

Fiiere

ncrcarea programelor n sistem se poate face prin dou predicate predefinite, consult(Fiier) i reconsult(Fiier). Efectul lui consult este acela c toate clauzele din Fiier sunt citite i vor fi folosite de Prolog atunci cnd va rspunde ntrebrilor puse de utilizator. Dac mai este consultat i alt fiier, clauzele din acesta vor fi adugate la sfritul setului de clauze n baza de cunotine Prolog.
Prolog poate accepta i comenzi direct de la terminal, care corespunde pseudo-fiierului user. Dup evaluarea scopului:

?- consult(user) Prolog ateapt introducerea clauzelor de la terminal.


Exist o prescurtare a comenzii de consultare a fiierelor. Fiierele sunt consultate dac numele lor sunt puse ntr-o list scris ca scop. De exemplu: ?- [fisier1, fisier2, fisier3]

este echivalent cu:


?- consult(fisier1), consult(fisier2), consult(fisier3). Predicatul reconsult(Fiier) are acelai efect ca i consult(Fiier), cu o singur excepie. Dac exist clauze n Fiier despre o relaie care a fost definit anterior, vechea definiie va fi suprascris de noile clauze despre relaie din Fiier. Diferena dintre consult i reconsult este aceea c predicatul consult adaug ntotdeauna noi clauze, n timp ce reconsult redefinete relaii definite anterior. Reconsultarea cu reconsult nu va afecta ns relaiile despre care nu exist nici o clauz n Fiier. Detaliile despre (re)consultare depind de implementarea de Prolog. n ARITY Prolog, la reconsultare, redefinirea unor relaii poate fi parial. ARITY Prolog are i alte predicate predefinite pentru manipularea fiierelor i bazei de cunotine Prolog (ele sunt descrise n fiierul arity.hlp). De exemplu, predicatul listing() listeaz clauzele din baza de date. Intrarea i ieirea pentru date (altele dect cele asociate interogrii programului) sunt fcute prin proceduri predefinite. Fiierele sunt accesate secvenial. Exist un flux curent de date de intrare i un flux curent de date de ieire. Terminalul utilizatorului este tratat ca un fiier, fiind denumit user. 88

Comutarea ntre fluxuri de date se poate face cu:

see(Fisier) - fiierul Fisier devine fluxul curent de intrare tell(Fisier) - fiierul Fisier devine fluxul curent de ieire seen - nchide fluxul curent de intrare told - nchide fluxul curent de ieire

Fiierele pot fi citite i scrise n dou moduri: ca secvene de caractere i ca secvene de termeni; n al doilea caz termenii sunt urmai de punct i desprii de cel puin un blank sau un caracter sfrit de linie (CR). Procedurile predefinite pentru citirea i scrierea caracterelor i termenilor sunt:

read(Termen) - citete n Termen urmtorul termen din fluxul curent de intrare write(Termen) - scrie termenul Termen n fluxul curent de ieire put(CodCaracter) - scrie caracterul care are codul ASCII CodCaracter get0(CodCaracter) - citete urmtorul caracter i ntoarce codul lui ASCII get(CodCaracter) - citete urmtorul caracter tipribil i ntoarce codul lui ASCII

Pentru formatarea ieirii exist dou proceduri:

nl - scrie un caracter sfrit de linie tab(N) - scrie N caractere blank


Procedura name(Atom, ListCoduri) descompune i construiete atomi. ListCoduri este lista codurilor ASCII ale caracterelor din Atom. Exemple:

name( ana, [97, 110, 97] ). name( 'Ana', [65, 110, 97] ). name( 'Ana vine.', [65, 110, 97, 32, 118, 105, 110, 101, 46] ).

8.5
EP1.

Exerciii propuse
Fie urmtorul program Prolog: este(a). este(b). este(c). exista(X) :- este(X) ; true.

Cte soluii are fiecare din urmtoarele scopuri: ?- exista(A), exista(B), exista(C), A \= a, B \= b, C \= c. ?- exista(A), exista(B), exista(C), !, A \= a, B \= b, C \= c. ?- exista(A), !, exista(B), exista(C). EP2. S se scrie dou variante ale predicatului de calcul al sumei unei liste de ntregi: o variant n care calculul se face pe ramura de avans n recursivitate i o a doua variant n care calculul sumei se face pe ramura de revenire din recursivitate, afind lista parial la fiecare apel recursiv.
89

EP3. S se scrie un predicat care verific dac un numr ntreg pozitiv poate fi exprimat ca suma a dou ptrate perfecte. (Ex: 65 = 16 + 49 = 4 2 + 7 2 ). EP4. S se scrie un predicatul care citete n bucl numere i rspunde dac sunt prime sau nu, pn la ntlnirea unui numr negativ, folosind predicatul predefinit repeat. EP5. S se scrie un program care calculeaz numrul de zile dintre dou date exprimate sub forma Zi-Luna, presupunnd c datele se refer la acelai an. Caracterul - se va defini ca un operator infixat. Ex: interval(3-martie, 7-aprilie, I) produce I = 35. EP6. S se scrie un predicat care implementeaz operaia de diferen ntre dou mulimi. Mulimile sunt reprezentate prin liste. Ex: diferenta([1, 2, a, b], [b, 3, 2, d], D) produce D = [1, a]. EP7. S se scrie un program care citete propoziii simple (afirmaii i ntrebri, pe care le adaug n baza de cunotine Prolog), avnd una din formele: _ este un _. _ este o _. Un _ este un/o _. O _ este un/o _. Este _ un/o _ ? i rspunde adecvat (da, nu sau necunoscut) la ntrebri pe baza afirmaiilor introduse. Exemplu: Radu este un student. Un om este o fiin. Radu este un om. Este Maria o persoan? EP8. S se defineasca operatorii Prolog este, are, un etc. astfel nct s poata fi introduse clauze de forma: diana este secretara lui toma. maria are un pian. i sistemul s poata rspunde la ntrebri de tipul: ?-Cine este secretara lui toma. Cine=Diana ?-diana este Ce. Ce=secretara lui toma ?-maria are un Ce. Ce=pian EP9. S se scrie un program Prolog de evaluare a unei expresii aritmetice care conine variabile, constante ntregi, paranteze i operatorii: +, -, *, /. Valorile variabilelor sunt date

90

sub forma de fapte Prolog prin predicatul valoare(Variabila, Valoare); de exemplu valoare(x, 100) sau valoare(y, -50). EP10. S se scrie un program care determin negarea unei formule n logica cu propoziii. Conectorii logici considerai sunt: not, and, or i implies. S se dea definiii adecvate pentru aceti operatori. Ex: neaga(p implies (q and not r),E) va produce E = p and (not q or r) EP11. S se scrie predicatul cauta(Cuvant, F) care caut cuvntul Cuvant n fiierul text F. EP12. S se scrie predicatul cuburi(F1, F2), care citete numere ntregi din fiierul text F1 i scrie cuburile lor n fiierul text F2. EP13. Utiliznd predicatul bagof, scriei predicatul parti_mult(Mult, Submult) care calculeaz n Submult mulimea prilor mulimii Mult. Reprezentai mulimile sub form de liste. Exemplu: parti_mult( [1, 2, 3], [ [ ], [1], [2], [3], [1, 2], [1, 3], [2, 3] ] ).

9
9.1

Sortare i cutare
Metoda de sortare prin generare i testare

Utiliznd structura de control a limbajului Prolog, se poate realiza foarte simplu sortarea unei secvene de elemente utiliznd metoda generare i testare. Aceast metod de rezolvare a problemelor, utilizat n inteligena artificial dar puin potrivit pentru o sortare, are la baz urmtoarea idee: o component generatoare construiete soluii candidate i o a doua component, componenta de testare, verific fiecare soluie candidat pentru a vedea dac este sau nu o soluie a problemei. n acest fel se pot obine fie toate soluiile problemei, fie una singur. Aceast metod poate fi exprimat succint n Prolog astfel: gaseste(Solutie) :genereaza(Solutie), testeaza(Solutie).
Metoda este n general ineficient chiar n cazul problemelor tipice de inteligen artificial care necesit un proces de cutare a soluiei. Metoda este extrem de ineficient n cazul rezolvrii problemelor de sortare, pentru care exist algoritmi eficieni de rezolvare. Cu toate acestea, se prezint n continuare soluia de sortare n ordine cresctoare a unei liste de ntregi prin metoda generare i testare ca exerciiu Prolog.

% sortare(+Lista, -ListaSortata) - sortare prin metoda generare i testare sortare(Lista, ListaSortata) :- permut(Lista, ListaSortata), ordonat(ListaSortata).

91

% permut(+Lista, -PermutareLista) permut([], []). permut(Lista, [Prim | Rest]) :- elim(Prim, Lista, L), permut(L, Rest). % elim(+Element, +Lista, -ListaMinusElement) elim(Elem, [Elem | Rest], Rest). elim(Elem, [Prim | Rest], [Prim | L]) :- elim(Elem, Rest, L). % rel(+X, +Y) - verific dac X i Y respect relaia de ordine rel rel(X, Y) :- X =< Y. % ordonata(+Lista) - verific dac Lista este sortat dup relaia rel(X, Y) ordonata([ _ ]). ordonata([Prim, Secund | Rest]) :- rel(Prim, Secund), ordonata([Secund | Rest]).
Se observ c sortarea se face prin generarea permutrilor elementelor din list i verificarea dac o permutare generat este o secven sortat. Componenta generatoare este predicatul permut(Lista, ListaSortata) i componenta de testare este predicatul ordonata(Lista). Dei implementarea este simpl, exploatnd facilitile nedeterministe ale limbajului Prolog, ea este foarte ineficient.

9.2

Metoda de sortare prin inserie

Sortarea prin inserie a unei liste de elemente L = [H | T] se poate exprima recursiv astfel: se sorteaz mai nti lista T n TS i apoi se insereaz elementul H n lista TS acolo unde i este locul conform relaiei de ordine rel(X, Y). % isort1(+Lista, -ListaSortata) - sortare prin insertie, varianta 1 isort1([], []) :- !. isort1([H | T], LS) :- isort1(T, TS), insereaza(H, TS, LS). % rel(+X, +Y) - verifica daca X si Y respecta relatia de ordine rel(X, Y) :- X =< Y. % insereaza(+Element, +Lista, -ListaPlusElement) % insereaza Element in Lista sortata astfel incat ListaPlusElement sa ramana sortata insereaza(Elem, [], [Elem]). insereaza(Elem, [Prim | Rest], [Elem, Prim | Rest]) :- rel(Elem, Prim), !. insereaza(Elem, [Prim | Rest], [Prim | L]) :not rel(Elem, Prim), insereaza(Elem, Rest, L). Predicatul cut folosit n definirea predicatului insereaza este un cut verde. Se poate rescrie predicatul insereaza folosind un cut rou astfel: insereaza(Elem, [], [Elem]).
92

insereaza(Elem, [Prim | Rest], [Elem, Prim | Rest]) :- rel(Elem, Prim), !. insereaza(Elem, [Prim | Rest], [Prim | L]) :- insereaza(Elem, Rest, L).
A doua variant de sortare prin inserie este urmtoarea: Lista poate fi privita la un moment dat ca L' = PS::PN, unde PS este partea sortat i PN = [X | T] este partea nesortat. Se ia primul element X din PN i se insereaz n PS. Algoritmul pornete cu PS = [] i PN = L, i se oprete cnd PN = [], n acel moment PS fiind chiar lista sortat. Se observa c PS are rol de acumulator, rezultatul final fiind ntors n al treilea parametru (constructia rezultatului se face pe apelul recursiv).

% isort2(+Lista, -ListaSortata) - sortare prin insertie, varianta 2 isort2(L, LS) :- isort2( [], L, LS). isort2(PS, [], PS). isort2(PS, [X | T], LS) :- insereaza(X, PS, PS1), isort2(PS1, T, LS).

9.3

Metoda de sortare rapid


1. Elimin un element Pivot din lista L i obine Rest = L - Pivot. 2. mparte lista Rest n dou liste: ListaInf, cu toate elementele din Rest inferioare elementului Pivot i ListaSup cu toate lementele din Rest superioare elementului Pivot. 3. Sorteaz ListaInf i obine ListaInfSort. 4. Sorteaz ListaSup i obine ListaSupSort. 5. Concateneaz listele ListaInfSort, lista formata din Pivot, ListaSupSort i obine ListaSortat.

Sortarea rapid (quicksort) a unei liste de elemente L se definete recursiv astfel:

Predicatul quicksort(Lista, ListaSortat), definit mai jos, implementeaz acest algoritm. Elementul Pivot este considerat, n implementare urmtoare, primul element al listei Lista, iar mprirea listei n ListaInf i ListaSup n funcie de elementul pivot se face cu ajutorul predicatului scindeaz(Element, Lista, ListaInf, ListaSup).

% quicksort(+Lista, -ListaSortata) - sortare prin metoda sortrii rapide quicksort([], []). quicksort([Pivot | Rest], ListaSortata) :scindeaz(Pivot, Rest, L1, L2), quicksort(L1, L1Sortata), quicksort(L2, L2Sortata), % conc(+L1, +L2, -L) - concateneaz listele L1 i L2 n lista L conc(L1Sortata, [Pivot | L2Sortat], ListaSortata). conc([], L, L). conc([Prim | Rest], L, [Prim | Rest1]) :- conc(Rest, L, Rest1). % scindeaza(+Element, +Lista, -ListaInf, -ListaSup) % mparte Lista n ListaInf (cu elemente inferioare lui Element) i ListaSup % (cu elemente superioare lui Element) n functie de relaia rel(X, Y)
93

scindeaza(Elem, [], [], []). scindeaza(Elem, [Prim | Rest], [Prim | L1], L2) :not rel(Elem, Prim), !, scindeaza(Elem, Rest, L1, L2). scindeaza(Elem, [Prim | Rest], L1, [Prim | L2]) :rel(Elem, Prim), scindeaza(Elem, Rest, L1, L2).
S testm acum implementrile metodelor de sortare prezentate i s comparm rezultatele lor cu rezultatul produs de predicatul sort, care este predefinit n ARITY Prolog:

% testarea metodelor de sortare test(F) :- L = [2, 2, 4, 6, 9, 8, 1, 3, 5, 7, 0], P =.. [F, L, S], call(P), write(P), nl. test :- test(isort1), test(isort2), test(quicksort), test(sort). Testarea va decurge astfel: ?- test. isort1([2,2,4,6,9,8,1,3,5,7,0], [0,1,2,2,3,4,5,6,7,8,9]) isort2([2,2,4,6,9,8,1,3,5,7,0], [0,1,2,2,3,4,5,6,7,8,9]) quicksort([2,2,4,6,9,8,1,3,5,7,0], [0,1,2,2,3,4,5,6,7,8,9]) sort([2,2,4,6,9,8,1,3,5,7,0], [0,1,2,2,3,4,5,6,7,8,9])

9.4

Arbori binari
1) arborele vid este codificat cu nil; 2) un arbore nevid este codificat SubarboreDrept).

Fie reprezentarea n Prolog a arborilor binari definit n seciunea 5.4: cu arb(Cheie, SubarboreStang,

Traversarea unui arbore binar, cu afiarea cheilor din noduri, se implementeaz n Prolog foarte uor folosind facilitile recursive ale limbajului. Predicatul rsd(Arbore) realizeaz afiarea cheilor din Arbore n ordinea rdcin-stnga-dreapta.

% rsd(+Arbore) - parcurge arborele binar Arbore % n ordinea rdcin-stnga-dreapta, afind cheile arborelui rsd(nil). rsd(arb(Radacina, SubarboreStang, SubarboreDrept)) :write(Radacina), write(' '), rsd(SubarboreStang), rsd(SubarboreDrept).
Dac se consider cazul arborilor binari de cutare (cu chei ntregi), se pot defini trei predicate: caut(Cheie, Arbore), de cutare a unei chei n arbore, care reuete dac cheia este n arbore i eueaz n caz contrar; inser(Cheie, Arbore, ArboreRez), de inserare a unei chei n arbore, cu argumentele Cheie i Arbore instaniate i argumentul ArboreRez sintetizat de program; i elim(Cheie, Arbore, ArboreRez), care terge o cheie dintr-un arbore.

% caut(+Cheie, +Arbore) - reuete dac Cheie este n % arborele binar de cutare Arbore, eueaz n caz contrar caut(Cheie, arb(Cheie, _ , _ )) :- !.
94

caut(Cheie, arb(Radacina, ArbStg, _)) :- Cheie < Radacina, caut(Cheie, ArbStg). caut(Cheie, arb(Radacina, _ , ArbDr)) :- Cheie > Radacina, caut(Cheie, ArbDr).
Prima clauz a predicatului caut reuete dac cheia este n arbore. Pentru a impiedica o posibil resatisfacere, deci gsirea unei alte apariii a cheii de cutare n arbore, s-a introdus n acest caz predicatul cut. Dac se doreste, de exemplu, afiarea tuturor apariiilor cheii de cutare n arbore, se va elimina acest cut i predicatul caut va avea attea soluii cte apariii ale cheii de cutare exist n arbore.

% inser(+Cheie, +Arbore, -ArboreRez) - insereaz Cheie n % arborele binar de cutare Arbore i produce ArboreRez inser(Cheie, nil, arb(Cheie, nil, nil)). inser(Cheie, arb(Cheie, ArbStg, ArbDr), arb(Cheie, ArbStg, ArbDr)):-!. inser(Cheie, arb(Radacina, ArbStg, ArbDr), arb(Radacina, ArbStg1, ArbDr)) :Cheie < Radacina, !, inser(Cheie, ArbStg, ArbStg1). inser(Cheie, arb(Radacina, ArbStg, ArbDr), arb(Radacina, ArbStg, ArbDr1)) :Cheie > Radacina, inser(Cheie, ArbDr, ArbDr1).
Predicatul de inserare a unei chei ntr-un arbore de cutare, inser, utilizeaz n definiie un cut verde pentru creterea eficientei. Se poate elimina condiia Cheie > Radacina, din cea de a treia regul a predicatului inser, caz n care predicatul cut se transform ntr-un cut rou. Programul Prolog care urmeaz folosete definiiile predicatelor caut i inser pentru a implementa mai multe operaii de prelucrare a arborilor binari de cutare. Eliminarea unei chei dintr-un arbore binar de cutare se face dup algoritmul standard:

% elim(+Cheie,+Arb,-ArbNou) elimina Cheie din Arb cu rezultat in ArbNou elim(Cheie, nil, nil). elim(Cheie, arb(Cheie, nil, nil), nil). elim(Cheie, arb(Cheie, ArbStg, nil), ArbStg). elim(Cheie, arb(Cheie, nil, ArbDr), ArbDr). elim(Cheie, arb(Cheie, ArbStg, ArbDr), arb(Cheie1, Stg1, ArbDr)) :drept(ArbStg, Cheie1, Stg1). elim(Cheie, arb(Cheie1, ArbStg, ArbDr), arb(Cheie1, ArbStg1, ArbDr1)) :(Cheie < Cheie1, !, elim(Cheie, ArbStg, ArbStg1), ArbDr1=ArbDr) ; elim(Cheie, ArbDr, ArbDr1), ArbStg1=ArbStg. % drept(+Arb,+Cheie,-SuccDr) - intoarce cel mai din dreapta succesor din % subarborele stng al nodului cu cheia Cheie in arborele Arb. drept(arb(Cheie, ArbStg, nil), Cheie, ArbStg). drept(arb(Cheie, ArbStg, ArbDr), Cheie1, arb(Cheie, ArbStg, ArbDr1)) :drept(ArbDr, Cheie1, ArbDr1).
Se poate defini un meniu de prelucrare arborilor binari de cutare care s permit execuia operaiilor definite anterior, la cerere.

meniu(Arb):- nl, write('1. Sfarsit'), nl,


95

write('2. Creeaza arbore'), nl, write('3. Insereaza o cheie'), nl, write('4. Cauta o cheie'), nl, write('5. Sterge o cheie'), nl, write('6. Afiseaza arbore'), nl, read(Opt), Opt \= 1, !, aciune(Opt, Arb, ArbNou), meniu(ArbNou). meniu( _ ) :- write('Sfarit'), nl. actiune(2, Arb, ArbNou) :- creare(Arb, ArbNou). actiune(3, Arb, ArbNou) :write('Introduceti cheia: '), read(Cheie), inser(Cheie, Arb, ArbNou). actiune(4, Arb, Arb) :write('Introduceti cheia: '), read(Cheie), (caut(Cheie, Arb), write('Cheia a fost gasita'); write('Cheia nu este in arbore')), nl. actiune(5, Arb, ArbNou) :write('Introduceti cheia: '), read(Cheie), elim(Cheie, Arb, ArbNou). actiune(6, Arb, Arb) :write('In ce ordine? '), read(Ordine), afisare(Arb, Ordine). creare(Arb, ArbNou) :write('Introduceti o cheie, 0 pentru terminare: '), read(Cheie), Cheie \= 0, !, inser(Cheie, Arb, A), creare(A, ArbNou). creare(Arb, Arb). % afisare(Arbore, Ordine) - afieaza cheile arborelui binar Arbore, parcurgnd % arborele n ordinea specificat de Ordine: rsd, srd, sdr afisare(nil, _ ). afisare(arb(Radacina, SubarboreStang, SubarboreDrept), Ordine) :(Ordine = rsd, write(Radacina), write(' '); true), afisare( SubarboreStang, Ordine), (Ordine = srd, write(Radacina), write(' '); true), afisare( SubarboreDrept, Ordine), (Ordine = sdr, write(Radacina), write(' '); true).
Afiarea arborilor cu ajutorul predicatului afisare(Arbore, Ordine) se poate face n trei moduri diferite, n funcie de valoarea argumentului Ordine: rsd (rdcin-stnga-dreapta), srd (stnga-rdcin-dreapta) i sdr (stnga-dreapta-rdcin). Se observ utilizarea operatorului de disjuncie a scopurilor cu ajutorul cruia se exprim cele trei modaliti de parcurgere a arborelui. Afiarea repetat a meniului de aciuni este fcut de predicatul meniu(Arb) prin apelul recursiv al acestuia ct timp nu s-a selectat aciunea Sfarsit. Predicatul are argumentul Arb instaniat. Acesta 96

poate fi iniial arborele vid i este actualizat de apelul recursiv la noua valoare ArbNou, care depinde de aciunea selectat. n funcie de aciunea selectat n NrActiune i de arborele dat Arb, predicatul actiune(NrActiune, Arb, ArbNou) decide care prelucrri sunt necesare pentru a obine arborele rezultat ArbNou. S vedem acum un predicat de sortare a listelor, care utilizeaz arbori binari de cutare. Predicatul binsort(Lista, ListaSortata) sorteaz lista Lista n lista ListaSortata. El construiete un arbore binar de cutare prin inserarea succesiv a elementelor din Lista cu ajutorul predicatului inser prezentat anterior. ListaSortata se obine prin parcurgerea n ordine a arborelui obinut.

% binsort(+Lista, -ListaSortata) binsort(L, LSort) :constr_arb(L, Tree, nil), write($Arborele este: $), nl, write(Tree), nl, parc_ord(Tree, LSort, []). % constr_arb - construiete un arbore binar de cutare constr_arb([], Acc, Acc) :- !. constr_arb([K | Rest], Sol, Acc) :inser(K, Acc, NoulAcc), constr_arb(Rest, Sol, NoulAcc). % parc_ord - parcurge n ordine un arbore binar, construind lista cheilor sale parc_ord(nil, Acc, Acc) :- !. parc_ord(nod(Cheie, Stang, Drept), L, Acc) :parc_ord(Stang, L1, Acc), n_ord(Drept, L, [Cheie | L1]).

9.5

Exerciii propuse

EP1. Care este complexitatea timp a algoritmului de sortare prin metoda generare i testare, prezentat n Seciunea 9.1 ? EP2. Comentai asupra complexitii timp a unei implementari Prolog a algoritmului de cutare binar a unei chei ntr-o list sortat cresctor. EP3. Scriei predicatul ssort(-L,+LS) care sorteaz lista L n lista LS conform metodei de sortare prin selecie. La un moment dat lista este L' = PS::PN, unde PS este partea sortat i PN partea nesortat. Se extrage n mod repetat din PN elementul X de valoare minim i se adaug la sfritul listei PS. Algoritmul ncepe cu PS = [] i PN = L, i se termin cnd PN = [], n acel moment PS fiind chiar lista sortat LS. Pentru eliminarea unui element dintr-o list i concatenarea a dou liste trebuie s mai scriei dou predicate: elim i append. Pentru a evita folosirea lui append se poate extrage maximul din PN la fiecare pas i insera naintea lui SP. EP4. S se scrie un program Prolog care realizeaz sortarea prin interclasare.

EP5. O concordan este o list de cuvinte care apar ntr-un text, lista sortat n ordine alfabetic mpreun cu numrul de apariii ale fiecrui cuvnt n text. S se scrie un program

97

care genereaz o concordan pornind de la un text dat. S se propun o reprezentare pentru text i o reprezentare pentru concordan. EP6. S se scrie un predicat care elimin o cheie dintr-un arbore binar de cutare.

EP7. S se rescrie predicatul meniu(Arb), nlocuind definiia recursiv a acestuia cu o definiie care utilizeaz predicatul repeat. EP8. S se scrie un predicat Prolog care verific egalitatea a doi arbori binari, adic dac au aceleai chei. EP9. S se scrie un predicat Prolog care verific egalitatea structural a doi arbori binari, adic dac au aceleai numr de chei i arat la fel. EP10. S se defineasc o structur Prolog care s reprezinte un arbore multici. S se scrie un predicat Prolog pentru afiarea unui astfel de arbore. EP11. S se scrie un predicat Prolog care verific egalitatea a doi arbori multici.

98

99

10 Probleme rezolvabile prin backtracking


Mecanismul implicit de funcionare a limbajului Prolog este bazat pe backtracking. De aceea problemele care se rezolv prin backtracking se implementeaz mai uor n Prolog dect n alte limbaje. S studiem cteva probleme rezolvabile prin backtracking.

10.1

Problema ranului

Fie urmtoarea problem: Pe malul unui ru se afl un ran cu un lup, o capr i o varz. ranul dorete s traverseze cu ele rul. ranul poate face traversri not ale rului mpreun doar cu unul din cele trei personaje de transportat, sau singur. Dac lupul rmne pe acelai mal mpreun cu capra i ranul este pe cellalt mal, capra va fi mncat de lup. Similar se ntmpl cu varza i capra. S se scrie un program Prolog care s i dea ranului toate soluiile de traversare.
Vom prezenta o rezolvare pe baz de backtracking. Definim starea problemei la un moment dat ca fiind:

stare(Taran, Lup, Capra, Varza) unde Taran, Lup, Capra i Varza indic poziia (malul) fiecarui element i pot avea valorile stang sau drept. n acest fel tim exact pe ce mal se afla fiecare dintre personaje. De remarcat c aceasta este o structur i nu un predicat. De aici rezult c starea inial este (de exemplu): stare(stang, stang, stang, stang) iar starea final este: stare(drept, drept, drept, drept).
Rezolvarea problemei prin backtracking presupune ncercarea unei micri posibile n starea curent i apoi reluarea algoritmului. O micare genereaz o nou stare, din care se vor ncerca apoi alte micri. Dac se ajunge ntr-o stare final, s-a gsit o solutie. n acest caz trebuie fcut o revenire pentru a ncerca gsirea altor soluii. Trebuie specificate, evident, starea iniial i starea sau strile finale. Dac se ajunge ntr-o stare ilegal, cum ar fi de exemplu, n cazul acestei probleme, stare(stang, stang, drept, drept), se execut tot o revenire n starea anterioar. Acelai lucru trebuie s se ntmple i dac starea curent este una n care s-a mai ajuns o dat. Nu are rost s trecem de mai multe ori prin aceeai configuraie deoarece acest lucru nu face dect s lungeasca numrul de micri necesare i poate conduce la bucle infinite. Deci vom avea nevoie de un predicat care, dndu-se o stare, s tie s execute micrile posibile din acea stare. Fie acest predicat:

miscare(Stare, StareUrmatoare). S vedem cum se definete predicatul micare n cazul acestei problemei: 1) ranul poate lua lupul cu el pe cellat mal: miscare(stare(Taran, Taran, Capra,Varza), stare(Taran1, Taran1, Capra,Varza)) :opus((Taran, Taran1).
106

2) ranul poate lua capra cu el pe cellat mal: miscare(stare(Taran, Lup, Taran,Varza), stare(Taran1, Lup, Taran1, Varza)) :opus(Taran, Taran1). 3) ranul poate lua varza cu el pe cellat mal: miscare(stare(Taran, Lup, Capra, Taran), stare(Taran1, Lup, Capra, Taran1)) :opus(Taran, Taran1). 4) ranul poate traversa singur rul: micare(stare(Taran, Lup, Capr, Varza), stare(Taran1, Lup, Capra, Varza)) :opus(Taran, Taran1).
Se observ c am folosit predicatul opus(Mal1, Mal2), care determin malul opus unui mal dat. Definiia lui este:

opus(stang, drept). opus(drept, stang).


Predicatele initiala(S) i finala(S) specific dac o stare este sau nu iniial, respectiv final. Definiiile lor sunt:

initiala(stare(stang, stang, stang, stang)). finala(stare(drept, drept, drept, drept)). Predicatul ilegala(Stare) spune dac o stare este ilegal: 1) Dac lupul este pe acelai mal cu capra i ranul este pe cellalt mal: ilegala(stare(Taran, Lup, Lup, _ )) :- opus(Taran, Lup). 2) Dac varza este pe acelai mal cu capra i ranul este pe cellalt mal: ilegala(stare(Taran, _ ,Capra, Capra)) :- opus(Taran, Capra).
Se poate scrie acum predicatul de gsire a unei succesiuni de mutri care rezolv problema. Deoarece nu trebuie s repetam stri, predicatul va primi, pe lng starea curent, i o list de stri deja parcurse, ntorcnd lista de stri (Solutie) care constituie rezolvarea:

lcv(+Stare, -Solutie, -Vizitate). unde Stare este starea curent, Solutie este soluia ce trebuie gsit, iar Vizitate este lista de stri parcurse. Acest predicat se scrie innd cont de: 1) Dac starea curent este o stare final, atunci soluie este lista de stri parcurse mpreun cu starea curent: lcv(Stare, [Stare|Vizitate], Vizitate) :- finala(Stare). 2) Altfel genereaz o nou mutare, testeaz dac este legal, testeaz dac nu a mai fost parcurs i relanseaz cutarea din noua stare:
107

lcv(Stare, Solutie, Vizitate) :miscare(Stare, StareUrmatoare), not(ilegala(StareUrmatoare)), not(member(StareUrmatoare, [Stare | Vizitate])), lcv(StareUrmatoare, Solutie, [Stare | Vizitate]).

% o nou mutare % este ilegal? % este deja parcurs? % reia cautarea

Predicatul member testeaz dac un element face parte dintr-o list i a fost detaliat anterior. Predicatul rezolva rezolv problema, ntorcnd lista strilor parcurse din starea iniial n starea final, deci soluia problemei.

rezolva(Solutie) :- initiala(Stare), lcv(Stare, Solutie, [] ).

10.2

Problema misionarilor i canibalilor

Trei misionari i trei canibali ajung la malul estic al unui ru. Aici se afl o barc cu dou locuri cu care se poate traversa rul (rul nu se poate traversarea not deoarece n el triesc peti piranha). Dac pe unul dintre maluri numrul de canibali este mai mare dect numrul de misionari, atunci misionarii de pe acel mal vor fi mncai de canibali. Problema ntreab cum pot trece toi rul fr ca misionarii s fie mncai de canibali. Pentru a afla soluia vom parcurge paii de la problema anterioar:
Structura strii curente este: stare(MalBarca, NMisionariVest, NCanibaliVest, NMisionariEst, NCanibaliEst).

rezolva(Solutie) :- initiala(Stare), mc(Stare, Solutie, [] ). % mc(+Stare, -Solutie, +StariVizitate) mc(Stare, [Stare | Vizitate], Vizitate) :- finala(Stare). mc(Stare,Soluie,Vizitate) :miscare(Stare, StareUrmatoare), not(ilegala(StareUrmatoare)), not(member(StareUrmatoare, [Stare | Vizitate])), mc(StareUrmatoare, Solutie, [Stare | Vizitate]). % miscare(+Stare, -StareUrmatoare) miscare(stare(est, MV,CV, ME, CE), stare(vest, MV1, CV, ME1, CE)) :oameni(N), ME >= N, ME1 is ME - N, MV1 is MV + N. miscare(stare(est, MV, CV, ME, CE), stare(vest, MV, CV1, ME, CE1)) :oameni(N), CE >= N, CE1 is CE - N, CV1 is CV + N. miscare(stare(est, MV, CV, ME, CE), stare(vest, MV1, CV1, ME1, CE1)) :ME >= 1, CE >= 1, ME1 is ME - 1, MV1 is MV + 1, CE1 is CE - 1, CV1 is CV + 1. miscare(stare(vest, MV, CV, ME, CE), stare(est, MV1, CV, ME1, CE)) :oameni(N), MV >= N, MV1 is MV - N, ME1 is ME + N.
108

miscare(stare(vest, MV, CV, ME, CE), stare(est, MV, CV1, ME, CE1)) :oameni(N), CV >= N, CV1 is CV - N, CE1 is CE + N. miscare(state(vest, MV, CV, ME, CE), stare(est, MV1, CV1, ME1, CE1)) :MV >= 1, CV >= 1, MV1 is MV - 1, ME1 is ME + 1, CV1 is CV - 1, CE1 is CE + 1. oameni(1). oameni(2). % ilegala(+Stare) ilegala(stare( _ , MV,CV , _ , _ )) :- MV > 0, CV > MV. ilegala(stare( _ , _ , _ , ME, CE)) :- ME > 0, CE > ME. initiala(stare(est, 0, 0, 3, 3)). finala(stare(vest, 3, 3, 0, 0)).

10.3

Problema gleilor cu ap

Exist dou glei cu capacitile de 8 i respectiv 5 litri, fr alte marcaje. Se cere s se msoare exact 4 litri dintr-un vas mare care conine cel puin 20 de litri. Operaiile admise sunt: umplerea unei glei din vasul mare, golirea unei gleti n vasul mare i transferul coninutului unei glei n alt gleat, pn cnd gleata din care se toarna s-a golit complet, sau gleata n care se toarn s-a umplut pn la refuz.
Pentru rezolvarea problemei vom urmri aceeai schem de la cele dou problemele anterioare.

rezolva(Solutie) :- initiala(Stare), galeti(Stare, [], Solutie). galeti(Stare, Solutie, Vizitate) :miscare(Stare, StareUrmatoare), % se genereaza starea urmatoare not(member(StareUrmatoare, [Stare | Vizitate])), % starea a mai fost generata? galeti(StareUrmatoare, Solutie, [Stare | Vizitate]). % daca nu, mergem mai departe
O stare este o structur cu cinci cmpuri: stare(D1, D2, G1, G2, M), care au urmtoare semnificaie: D1 = ci litri de ap sunt n gleata 1, D2 = ci litri de ap sunt n gleata 2, G1 = capacitatea gleii 1, G2 = capacitatea gleii 2, M = mesajul explicativ asociat tranziiei n aceast stare (M spune ce aciune a fost executat cnd s-a trecut n aceast stare).

% miscare(+Stare,-StareUrmatoare) % Regulile comentate nu sunt necesare, ele genernd soluii sau execuii mai lungi. miscare(stare(D1, D2, G1, G2, _ ), stare(0, D2, G1, G2, 'Golesc G1 in vas.')). miscare(stare(D1, D2, G1, G2, _ ), stare(D1, 0, G1, G2, 'Golesc G2 in vas.')). miscare(stare(D1, D2, G1, G2, _ ), stare(G1, D2, G1, G2, 'Umplu G1 din vas.')). miscare(stare(D1, D2, G1, G2, _ ), stare(D1, G2, G1, G2, 'Umplu G2 din vas.')). miscare(stare(D1, D2, G1, G2, _ ), stare(0, D22, G1, G2, 'Golesc G1 in G2.')) :D1 > 0, D22 is D1 + D2, D22 =< G2.
109

% miscare(stare(D1, D2, G1, G2, _ ), stare(D11, 0, G1, G2, 'Golesc G2 in G1.')) :% D2 > 0, D11 is D1 + D2, D11 =< G1. miscare(stare(D1, D2, G1, G2, _ ), stare(D11, G2, G1, G2, M)) :T is G2 - D2, D11 is D1 - T, D11 > 0, string_ term(S, T), concat(['Torn', S, ' litri din G1 in G2.'], M). % miscare(stare(D1, D2, G1, G2, _ ), stare(G1, D22, G1, G2, M)) :% T is G1 - D1, D22 is D2 - (G1 - D1), D22 > 0, % string_ term(S, T), concat(['Torn', S, ' litri din G2 in G1.'], M).
Se observ c problema a fost rezolvat pentru cazul general, putnd varia att capacitile gleilor, ct i numrul de litri de ap care trebuie msurat.

% Starea initiala initiala(stare(0, 0, 8, 5, 'Galetile sunt goale.')). % Starea finala finala(stare(4, _ , _ , _ , _ )). finala(stare( _ , 4, _ , _ , _ )).
Funcia member a fost rescris deoarece mesajul asociat unei stri nu trebuie s influeneze procesul de rezolvare a problemei. O stare este dat de fapt doar de primii doi parametrii ai structurii stare, urmtorii doi fiind dou valori constante, iar ultimul, mesajul, a fost introdus doar pentru a descrie rezolvarea problemei. Deci putem ajunge n aceeai stare cu dou mesaje diferite.

member(stare(X, Y, _ , _ , _ ), [stare(X, Y, _ , _ , _ ) | _ ] ). member(X, [ _ | Rest] ) :- member(X, Rest). Pentru afiarea frumoas a soluiilor se adaug predicatele: run :- rezolva(Solutie), scrie_solutie(Solutie). scrie_solutie([]) :- nl. scrie_solutie([S | Rest]) :- scrie_solutie(Rest), scrie_stare(S). scrie_stare(stare(X, Y, _ , _ )) :- write(X), write($ $), write(Y), nl. Funcionarea programului este urmtoarea: ?- run. 0 0 Galetile sunt goale. 8 0 Umplu G1 din vas. 3 5 Torn 5 litri din G1 in G2. 3 0 Golesc G2 in vas. 0 3 Golesc G1 in G2. 8 3 Umplu G1 din vas. 6 5 Torn 2 litri din G1 in G2.
110

6 0 1 5 1 0 0 1 8 1 4 5 yes

Golesc G2 in vas. Torn 5 litri din G1 in G2. Golesc G2 in vas. Golesc G1 in G2. Umplu G1 din vas. Torn 4 litri din G1 in G2.

10.4

Exerciii propuse

EP1. Fie un graf neorientat definit de legturile dintre nodurile sale, exprimate ca fapte Prolog: leg(a, b). leg(b, c). leg(a, c). leg(a, d). 1) S se defineasc predicatul drum(N1, N2), care verific dac exist un drum n graf ntre nodurile N1 i N2. 2) S se critice urmtoarea implementare a predicatului drum: drum(N, N). drum(N1, N2) :- leg(N1, N), drum(N, N2). 3) S se modifice definiia predicatului drum la drum(N1, N2, ListaN) astfel nct s se obin n ListaN lista nodurilor parcurse ntre N1 i N2, n cazul n care exist un drum n graf ntre nodurile N1 i N2, i lista vid n caz contrar. EP2. Studiind texte vechi, n ncercarea de a reconstitui arborele genealogic al unei familii boiereti, un istoric i-a notat pe fie separate, relaiile de rudenie dintre diferitele persoane ale unei singure familii, sub forma a trei tipuri de relaii:
x este soul (soia) lui y x este fiul (fiica) lui y x este fratele (sora) lui y

unde x si y sunt prenumele unor membri din aceeai familie.


In ipoteza c nu exist dou persoane cu acelai prenume, sa se scrie un program care citeste dintr-un fisier relaii de rudenie ntre perechi de persoane i care stabilete urmtoarele: - dac informaiile privind relaiile de rudenie sunt compatibile sau contradictorii, tiind c n familie nu s-au fcut cstorii ntre rude. In caz c informaiile sunt contradictorii se d mesajul 'Informaii incompatibile' i nu se mai fac alte operaii privind familia n cauz; - daca informaiile nu sunt complete pentru alctuirea arborelui genealogic, se afieaz numai mesajul 'Informaii incomplete'. Dac datele permit alctuirea arborelui genealogic, se afieaza mai nti relaiile de rudenie deduse n afara celor date iniial i se afieaz arborele genealogic sub o forma sugestiva. (Concurs de Programare UPB 1995, Etapa local).

11 Strategii de cutare n spaiul strilor


Unul dintre cele mai utilizate modele de rezolvare a problemelor este prin reprezentarea cutrii sub forma unui graf orientat n care nodurile sunt stri succesive n rezolvare iar
111

arcele corespund tranziiilor sau operatorilor legali ce pot fi aplicai pentru trecerea dintr-o stare n alta [Flo93,LS93].

11.1

Cutarea soluiilor n spaiul strilor

Pentru a construi o descrierea unei probleme rezolvate prin reprezentare n spaiul strilor trebuie parcurse urmtoarele etape: 1) Se definete spaiul strilor care conine toate toate configuraiile posibile ale obiectelor relevante (si poate si unele imposibile). Spaiul se poate defini explicit prin indicarea tuturor strilor (acesta fiind un caz particular i rar ntlnit n practic) sau implicit prin indicarea transformrilor care genereaz o nou stare dintr-o stare dat. 2) Se specific una sau mai multe stri din spaiu care descriu situaii posibile de la care poate porni procesul de rezolvare a problemei (starea iniial). 3) Se specific una sau mai multe stri care ar fi acceptabile ca soluii ale problemei. Aceste stri se numesc stri scop (sau stri finale sau scopuri). 4) Se specific un set de reguli care descriu aciunile (operatorii) disponibile i care definesc tranziiile sau transformrile ntre stri. n aceast etap trebuie analizate urmtoarele probleme: Ce presupuneri nedeclarate explicit sunt prezente n descrierea informal a problemei? Ct de generale trebuie s fie regulile? Ct de mult din calculul necesar rezolvrii problemei ar trebui fcut nainte i reprezentat n reguli?
Problema poate fi rezolvat apoi utiliznd o strategie de control adecvat, pentru a parcurge o parte din spaiul strilor (eventual tot) pn cnd este gsit o cale de la o stare iniial la o stare final, n cazul n care problema admite soluie. Cutarea este un mecanism general care poate fi utilizat atunci cnd o metod direct (determinist) de rezolvare a problemei nu este cunoscut. n acelai timp, ea ofer cadrul n care pot fi ncapsulate metode mai directe de rezolvare a anumitor pri ale problemei (subproblemelor).

11.2

Cutare prin backtracking

Rezolvarea unei probleme folosind strategia de backtracking a fost expus in capitolul 10. n continuare se prezint schema general a unei astfel de cutri care poate fi aplicat pentru orice descriere a unei subprobleme n termeni de stri iniiale, stri finale i operatori sau tranziii ntre stri. Pentru ilustrare, se definete explicit un spaiu de cutare finit, ipotetic, prin definirea predicatului succ(stare, stare_urmatoare). O definire a predicatului succ specific unei probleme particulare cuplat cu schema general de rezolvare prin backtracking ce este prezentat n continuare rezolv problema. % Graful care descrie spaiul de cutare complet.
112

succ(a,b). succ(b,c). succ(c,d). succ(d,g). succ(a,e). succ(e,f). succ(f,g). final(g).

% a stare iniial

% Stare final

% rez(+Stare, -Sol) rez(Stare, Sol) :- bkt(Stare, [], Sol). bkt(Stare, Drum, [Stare | Drum]) :- final(Stare). bkt(Stare, Drum, Sol) :succ(Stare, N1), not (member(N1, Drum)), bkt(N1, [Stare | Drum], Sol). ?- rez(a,Sol).
Sol=[g,d,c,b,a] Cutarea prin backtracking poate fi cuplat cu impunerea unei adncimi maxime de cutare, necesar n special n cazul spaiilor de cutare infinite n care soluia poate s nu se afle pe ramura curent pe care a nceput cutarea. Stabilirea unei adncimi maxime de cutare se poate face astfel:

% rez1(Stare, Sol) impune o adncimea maxim Max = 10 rez1(Stare, Sol) :- bkt1(Stare, Sol,10). bkt1(Stare, [Stare], _ ) :- final(Stare). bkt1(Stare, [Stare | Sol], Max) :Max > 0, succ(Stare, N1), Max1 is Max-1, bkt1(N1, Sol, Max1).
In acest caz, s-a eliminat testul de stri anterior parcurse deoarece, existnd o adncime maxim de cutare se elimin buclele dar cu penalizarea eventual a reparcurgerii unor stri. Exerciiul propus 1 al acestui capitol va cere o combinare a acestor dou soluii.

11.3 Cutare pe nivel i n adncime


Pentru implementarea acestor dou strategii de cutare de baz se folosesc dou liste: lista OPEN a nodurilor explorate n cutare (sau FRONTIERA poriunii cunoscute a spaiului de cautarea la un moment dat cu partea necunoscut nc a acestui spaiu) i lista CLOSED a nodurilor expandate (sau TERITORIUL poriunii cunoscute din spaiul de cutare). Detalii suplimentare asupra acestor strategii de cutare se pot gsi n [Flo93]. Pentru a putea obine calea de la starea iniial la starea final, odat ce s-a gsit starea final, ambele liste vor conine perechi [Stare, Predecesor], unde Predecesor este starea predecesoare a strii Stare. Pentru starea iniial se va introduce n OPEN perechea [StareInitial, nil], cu nil o constant arbitrar fixat.
Implementarea folosete dou tipuri de date, stiv i coad, pentru exploatarea listei OPEN, respectiv stiva n cazul cutrii n adncime i coada n cazul cutrii pe nivel. Definirea operaiilor 113

tipice asociate acestor structuri de date abstracte n Prolog este pus n eviden de implementarea ce urmeaz.

% Cutare pe nivel i n adncime % Se folosesc tipurile de date abstracte Stack and Queue. % TDA Stack % emptys(+S) - testeaz dac stiva este vid % emptys(-S) - iniializeaz stiva emptyst([]). % stack(-Top, -SNou, +S) - are efect de pop % stack(+Top, +S, -SNou) - are efect de push % stack(-Top, _, +S) - are efect de top stack(Top, Stack, [Top | Stack]). % TDA Queue % empty(+Q) - testeaz dac coada este vid % empty(-Q) - iniializeaz coada emptyq([]). % enqueue(+El, +Q, -QNou) introduce un element n coad enqueue(El, [], [El]). enqueue(El, [X | Rest], [X | R1]) :- enqueue(El, Rest, R1). % dequeue(-El, +Q, -QNou) elimin un element din coad dequeue(El, [El | R], R). % Spaiul de cutare succ(a, b). succ(b, c). succ(c, d). succ(d, g). succ(a, e). succ(e, f). succ(f, g). final(g). % Rezolvare cu parcurgere pe nivel % rezbreadth(+StareInitiala) rezbreadth(Si) :emptyq(Open), emptyq(Closed), enqueue([Si, nil], Open, Open1), breadth(Open1, Closed). breadth(Open, _) :- emptyq(Open), !, write('Nu exista solutie'), nl. breadth(Open, Closed) :dequeue([Stare | Predec], Open, _), final(Stare), write('S-a gasit o solutie'), nl, scriecale([Stare | Predec], Closed). breadth(Open, Closed) :dequeue([Stare| Predec], Open, _), final(Stare), write('S-a gasit o solutie'),nl,
114

showpath([Stare| Predec], Closed). breadth(Open, Closed) :dequeue([Stare, Pred], Open, RestOpen), enqueue([Stare, Pred], Closed, Closed1), listsucc(Stare, RestOpen, Closed1, LSucc), append(RestOpen, Lsucc, Open1), breadth(Open1, Closed1). listsucc(Stare, RestOpen, Closed, Lsucc) :bagof([S, Stare], (succ(Stare, S), not member([S,_], RestOpen), not member([S, _], Closed) ), LSucc). listsucc(Stare, RestOpen, Closed, []). % Rezolvare cu parcurgere pe n adncime % rezdepth(+StareInitiala) rezdepth(Si) :emptyst(Open), emptyst(Closed), stack([Si, nil], Open, Open1), depth(Open1, Closed). depth(Open, _) :- emptyst(Open), write('Nu exista solutie'), nl. depth(Open, Closed) :stack([Stare | Predec], RestOpen, Open), final(Stare), write('S-a gasit o solutie'), nl, scriecale([Stare | Predec], Closed). depth(Open, Closed) :stack([Stare, Pred], RestOpen, Open), stack([Stare, Pred], Closed, Closed1), listsucc(Stare, RestOpen, Closed1, LSucc), append(LSucc, RestOpen, Open1), depth(Open1, Closed1). % Afieaz calea de la Si la Sf % scriecale(+Cale, +Closed) scriecale([S, nil], _) :- scrie(S), nl. scriecale([S, Predec], Closed) :member([Predec, P], Closed), scriecale([Predec, P], Closed), scrie(S), nl. scrie(S) :- write(S). member(El, [El | _]). member(El ,[_ | Rest]) :- member(El, Rest). append([], L, L). append([X | L1], L2, [X | L3]) :- append(L1, L2, L3). ?- rezbreadth(a).
115

a e f g ?- rezdepth(a). a b c d g
Strategia de cutare pe nivel este o strategie complet care, n plus, gsete calea de la starea iniial la starea final cu numrul minim de tranziii de stri. Strategia de cautare n adncime nu este complet pentru orice spaii de cutare dar consum mai puin memorie dect cea n adncime. Pentru a pune n eviden diferena ntre strategia de cutare n adncime i cea de backtracking, se va rescrie schema general de backtracking din seciunea precedent pe acelai ablon cu cel de la strategiile precedente.

rezbkt(Si) :-emptyst(Open), stack(Si, Open, NewOpen), bkt(NewOpen). bkt(Open) :- stack(S, _, Open), final(S), write('S-a gsit o soluie'), afi(Open). bkt(Open) :stack(S, _, Open), succ(S, S1), not member(S1, Open), stack(S1, Open, NewOpen), bkt(NewOpen). afis([]) :- nl. afis([S | Rest]) :- afis(Rest), scrie(S).
Se observ c, deoarece strategia de backtracking pastreaz numai strile de pe calea curent de cutare i face testul pentru stri anterior parcurse numai pe aceast cale, este suficient meninerea numai a listei OPEN. In plus, aceast list nu mai trebuie s fie o list de perechi [Stare, Predecesor] ci numai o list de stri parcurse, la detecia strii finale coninutul lui OPEN fiind chiar calea spre soluie. Implementarea de mai sus este echivalent cu cea din seciunea precedent. Predicatul afis permite afiarea strilor parcurse n ordinea de la starea iniial la cea final.

11.4

Algoritmul A*

A* este un algoritm de cutare n spaiul strilor a soluiei de cost minim (soluia optim) ce utilizeaz o funcie euristic de estimare a distanei strii curent parcurse fa de starea final. Pe parcursul cutrii, strile sunt considerate n ordinea cresctoare a valorii funciei f(S)=g(S)+h(S), unde g(S) este funcia de cost a poriunii parcurse pn n starea S iar h(S) este funcia euristic de estimare a distanei din S la starea final. Funcia euristic trebuie s fie admisibil (mai mic sau cel mult egal cu distana real) pentru a garanta optimalitatea soluiei. O prezentare sistematic i detalii suplimentare asupra algoritmului A* pot fi gsite n [Flo93]. Dei are o complexitate timp tot exponenial, ca orice procedur de cutare, algoritmul este mai eficient, n general, dect stategiile neinformate datorit componentei euristice care ghideaz cutarea.

116

Trebuie observat c algoritmul A* poate fi aplicat i n cazul n care ntr-o problem nu exist costuri, considernd implicit toate costurile tranziiilor de stri egale cu 1. n acest caz, rezultatele algoritmului sunt similare cu cele ale parcurgerii pe nivel, n sensul gsirii drumului de la starea iniial la starea final cu un numr minim de tranziii de stri, dar cutarea este, n general, mai rapid. Programul Prolog ce urmeaz presupune, implicit, costurile tranziiilor de stri egale cu 1. Implementarea algoritmului A* se va face pe baza schemelor anterioare de cutare, cu urmtoarele modificri. n loc de a exploata lista OPEN ca o stiv sau ca o coada, acest list va fi tratat ca o list de prioriti n funcie de f(S). Fiecrei tranziii de stri i se va asocia un cost, n funcie de problem, i fiecrei stri i se va asocia o valoare pentru funcia euristic h(S). Lista OPEN va fi o list de liste de patru elemente de forma [Stare, Predecesor, G, H, F]. La fel ca n cazurile precedente, programul va fi exemplificat pe un spaiu de cutare ipotetic pentru ca n capitolul urmtoar schema general de cutare s fie aplicat pe probleme reale.

% Spaiul de cutare succ(a, b). succ(b, c). succ(c, d). succ(d, g). succ(a, e). succ(e, f). succ(f, g). final(g). % Valorile funciei euristice folosite n algoritmul A* euristic(a, g, 3). euristic(b, g, 3). euristic(c, g, 2). euristic(d, g, 1). euristic(g, g, 0). euristic(e, g, 2). euristic(f, g, 1). euristic(_, _, 0). % Coad de prioriti (coada este sortat crescator n funcie de cheia F1). inspq(El, [], [El]). inspq(El, [X | Rest], [El, X | Rest]) :- precedes(El, X), !. inspq(El, [X | Rest], [X | R1]) :- inspq(El, Rest, R1). precedes([_, _, _, _, F1], [_, _, _, _, F2]) :- F1<F2. rezastar(Si, Scop) :emptyq(Open), emptyq(Closed), euristic(Si, Scop, H), inspq([Si, nil, 0, H, H], Open, Open1), astar(Open1, Closed, Scop). astar(Open, _, _) :- emptyq(Open), !, write('Nu exista solutie'), nl. astar(Open, Closed, Scop) :dequeue([S, Pred, _, _, _], Open, _), S=Scop, write('S-a gsit o soluie'), nl,
117

scriecale1([S, Pred, _, _, _], Closed). astar(Open, Closed, Scop) :dequeue([S, Pred, G, H, F], Open, RestOpen), inspq([S, Pred, G, H, F], Closed, Closed1), (bagof([Urmator, H1], (succ(S, Urmator), euristic(Urmator, Scop, H1)), LSucc),!, G1 is G+1, actual_toti(S, G1, LSucc, RestOpen, Closed1, OpenR, ClosedR); OpenR=RestOpen, ClosedR=Closed1), astar(OpenR, ClosedR, Scop). actual_toti(_, _, [], Open, Closed, Open, Closed) :- !. actual_toti(Stare, G, [[S, H] | Rest], Open, Closed, OpenR, ClosedR) :actual(Stare, G, [S, H], Open, Closed, Open1, Closed1), actual_toti(Stare, G, Rest, Open1, Closed1, OpenR, ClosedR). actual(Stare, G, [S, H], Open, Closed, OpenR, Closed) :member([S, Pred, G1, _, _], Open), !, ( G1=<G, OpenR=Open, !; F is G+H, elim([S, Pred, G1, _, _], Open, Open1), inspq([S, Stare, G, H, F], Open1, OpenR)). actual(Stare, G, [S, H], Open, Closed, OpenR, ClosedR) :member([S, Pred, G1, _, _], Closed), !, ( G1=<G, ClosedR=Closed, OpenR=Open, !; F is G+H, elim([S, Pred, G1, _, _], Closed, ClosedR), inspq([S, Stare, G, H, F], Open, OpenR)). actual(Stare, G, [S, H], Open, Closed, OpenR, Closed) :F is G+H, inspq([S, Stare, G, H, F], Open, OpenR). scriecale1([S, nil, _, _, _], _) :- scrie(S), nl. scriecale1([S, Pred, _, _, _], Closed) :member([Pred, P, _, _, _], Closed), scriecale1([Pred, P, _, _, _], Closed), scrie(S), nl. scrie(S) :- write(S). elim(_, [], []). elim(X, [X | Rest], Rest) :- !. elim(X, [Y | Rest], [Y | Rest1]) :- elim(X, Rest, Rest1).
118

?- rezastar(a). a e f g

11.5

Exerciii propuse

EP1. S se rescrie schema de cautare cu backtracking combinnd varianta care memoreaz lista strilor parcurse cu cea care impune o adncime maxim de cutare. EP2. Adncimea maxim de cutare trebuie impus, n anumite cazuri (spaii de cutare infinite) i pentru cutarea n adncime. S se modifice schema cutarii n adncime prin includerea unei adncimi maxime de cutare. EP3. Strategiile de cutare backtracking, n adncime i pe nivel prezentate determin prima soluie, n cazul n care problema admite soluie. S se modifice programele corespunztoare acestor strategii astfel nct s determine i s afieze toate soluiile problemei, dac problema admite mai multe soluii. EP4. S se modifice algoritmul A* din seciunea 11.4 prin adugarea unui cost explicit, diferit de 1, tranziiilor de stri.

12 Utilizarea cutrii n rezolvarea problemelor


n acest capitol se aplic schemele de strategii de cutare dezvoltate n capitolul precedent pentru rezolvarea unor probleme "clasice" n inteligen artificial. n acest scop, pentru fiecare problem, se indic reprezentarea strilor cu identificarea strii iniiale i a strii finale, se definesc tranziiile legale de stri i, acolo unde este cazul, costuri i funcii euristice de ghidare a cutrii.

12.1

Problema misionarilor i canibalilor

Relum problema misionarilor i canibalilor din seciunea 10.2 i artm cum se poate rezolva acest problem pe baza schemelor de cutare prezentate n capitolul 11. Spre deosebire de rezolvarea din seciunea 10.2, se modific reprezentarea unei stri a problemei din
stare(MalBarca, NMisionariVest, NCanibaliVest, NMisionariEst, NCanibaliEst)

n
stare(MalBarca, NMisMalBarca, NCanMalbarca, NMisMalOpus, NCanMalOpus)

unde MalBarca poate fi, la fel ca nainte, est sau vest. Implementarea care urmeaz pune n eviden avantajele acestei noi reprezentri din punct de vedere al simplificrii prelucrrilor necesare.
119

Se consider starea iniial n care cei trei misionari i canibali sunt pe malul de est, ei dorind s ajung pe malul de vest fr ca misionarii s fie mncai de canibali.

% Stare iniial initial(st(est, 3, 3, 0, 0)). % Starea final final(st(vest, 3, 3, 0, 0)). % Malurile opuse opus(est, vest). opus(vest, est). % sigur(+NrMisionari, +NrCanibali) - stare sigur sigur(0, _ ). sigur(X, Y) :- X > 0, X >= Y. % Tranziii ntre stri, succ(+StareCurenta, - StareUrmatoare) % mut doi misionari succ(st(X, MX, CX, MY, CY), st(Y, MY1, CY, MX1, CX)) :opus(X, Y), modifica(2, MX, MY, MX1, MY1), sigur(MX1, CX), sigur(MY1, CY). % mut doi canibali succ(st(X, MX, CX, MY, CY), st(Y, MY, CY1, MX, CX1)) :opus(X, Y), modifica(2, CX, CY, CX1, CY1), sigur(MX, CX1), sigur(MY, CY1). % muta un misionar i un canibal succ(st(X, MX, CX, MY, CY), st(Y, MY1, CY1, MX1, CX1)) :opus(X, Y), modifica(1, MX, MY, MX1, MY1), modifica(1, CX, CY, CX1, CY1), sigur(MX1, CX1), sigur(MY1, CY1). % mut un misionar succ(st(X, MX, CX, MY, CY), st(Y, MY1, CY, MX1, CX)) :opus(X, Y), modifica(1, MX, MY, MX1, MY1), sigur(MX1, CX), sigur(MY1, CY). % mut un canibal succ(st(X, MX, CX, MY, CY), st(Y, MY, CY1, MX, CX1)) :opus(X, Y), modifica(1, CX, CY, CX1, CY1), sigur(MX, CX1), sigur(MY, CY1).
120

% modifica(+Cati, +NrInit1, +NrInit2, -NrRez1, -NrRez2) modifica(N, NX, NY, NX1, NY1) :- NX >= N, NX1 is NX - N, NY1 is NY + N. % Afieaz soluia % Predicatul scrie din capitolul precedent se redefinete astfel: scrie(st(B, MX, CX, MY, CY)) :- nl, scrielista([B, ' ', MX, ' ', CX, ' ', MY, ' ', CY]). scrielista([]). scrielista([X | Rest]) :- write(X), scrielista(Rest).
Rezolvarea problemei este acum imediat folosind schemele de cutare din capitolul precedent. Selecia predicatului rezbkt conduce la o rezolvare echivalent cu cea prezentat n seciunea 10.2.

% Se alege strategia dorit (nivel, adncime sau backtracking) solutie :nl, initial(Si), rezbreadth(Si). % rezdepth(st(est, 3, 3, 0, 0)). % rezbkt(Si).
Pentru a aplica o strategie A* se fixeaz implicit costuri de 1 pentru fiecare tranziie ntre dou stri i trebuie definit o funcie euristic admisibil. Se noteaz cu Ne numrul persoanelor de pe un anumit mal (misionari plus canibali) n starea S i se definesc urmtoarele trei funcii euristice1 : h1(S) = nE(S), numarul de persoane de pe malul de est n starea S h2(S) = nE(S) / 2 nE(S) + 1, dac barca este la vest i nE(S) 0 E h3(S) = n (S) - 1, dac barca este la est i nE(S) 0 0 dac nE(S) = 0 Funcia h1 nu este admisibil, funciile h2 i h3 sunt admisibile i monotone, cu h3 funcie euristic mai informat dect h2. Pentru a rezolva problema cu algoritmul A* se definete, de exemplu, funcia h3 n Prolog, se pstrez specificaiile anterioare ale problemei i se folosete strategia de cutare informat definit n seciunea 11.4. Soluia obinut este minim (optim) n termeni de numr de tranziii de stare.

% Funcia euristic h3 euristic(st(est, MX, CX, _ , _ ), Sfin, H) : final(Sfin), Ne is MX + CX, (Ne \= 0, !, H is Ne - 1; H = 0). euristic(st(vest, _ , _ , MY, CY), Sfin, H) : final(Sfin), Ne is MY + CY, (Ne \= 0, !, H is Ne + 1; H=0). % Rezolvare solutie :nl, initial(Si), final(Sfin), rezastar(Si, Sfin).

1 Datrm aceste funcii euristice profesorului C. Giumale.

121

12.2

Problema mozaicului cu opt cifre

Problema mozaicului cu opt cifre const n gsirea mutrilor de executat pentru a ajunge dintr-o configuraie iniial a mozaicului ntr-o configuraie final. O configuraie final poate fi cea prezentat n figura 11. 1 8 7 6 2 3 4 5

Figura 11. O configuraie final a mozaicului cu opt cifre


Aceast problem, dei aparent simpl, are un spaiu de cutare foarte mare n cazul general i este un bun exemplu ce pune n eviden utilitatea folosirii funciilor euristice de ghidare a cutrii. n seciunile ce urmeaz vom arata cum se rezolv problema cu diverse strategii i vom pune n eviden incapacitatea strategiilor de cutare neinformate de a gsi soluia pentru multe din configuraiile iniiale propuse. V sugerm s testai, pentru diverse poziii iniiale, care metode reuesc i care nu. Proprietile importante care determin euarea unor metode i reuita altora sunt numrul de stri generate, numrul de apeluri recursive i numrul de mutri care rezolv problema. Problema nu conine costuri dar, dac ne intereseaz rezolvarea mozaicului printr-un numr minim de micri (tranziii de stare) se poate considera costul tuturor tranziiilor egal cu 1. Pentru rezolvarea problemei printr-un algoritm A* se pot defini urmtoarele funcii euristice alternative [Bra88]:

1) h1(S) = numrul de cifre care nu sunt la locul lor n starea S fa de starea final; 2) h2(S) = suma distanelor, pe orizontal i vertical, la care se afl cifrele n starea S fa de poziia lor final (distana Manhattan); 3) h3(S) = h2(S) + 3 D(S), unde D(S) este sum din d(c) pentru fiecare cifr c, mai puin ultima, n starea S. Funcia d(c) se calculeaz astfel: Dac c este pe poziia n care se va afla n final spaiul liber, atunci d(c) = 1; Dac c este pe poziia p, iar c + 1 se afl pe poziia p + 1, atunci d(c) = 0; Dac c este pe poziia p, iar c + 1 nu se afl pe poziia p + 1, atunci d(c) = 2.
Funciile h1 i h2 sunt admisibile, h2 fiind mai informat dect h1 deoarece ncearc s surprind i dificultatea structural a configuraiei iniiale. Funcia h3 nu este admisibil deoarece n unele situaii este mai mare dect h* (distana real). Ea este util deoarece este mai informat dect h2 i ajunge mult mai repede la o soluie bun, chiar dac soluia nu este optim (soluia este destul de aproape de soluia optim). Aa cum se va vedea mai departe, h3 este singura funcie care poate rezolva problema pentru anumite configuraii iniiale. n continuare se va prezenta rezolvarea acestei probleme prin trei metode neinformate, backtracking, parcurgere n adncime, parcurgere pe nivel, apoi rezolvarea pe baza algoritmului A*, utiliznd funciile h1, h2 i h3. Se va vedea c o cutare n adncime funcioneaz pn la maxim 2-3 mutri necesare pentru rezolvarea problemei, cutarea pe nivel poate ajunge pn la 5-6 mutri i A* cu h1 pn la 8-9 mutari, cu h2 pn la 18-20 de mutri, iar cu h3 chiar pn la 25 de mutri.

122

12.3

Rezolvarea problemei mozaicului prin cutri neinformate

O prim posibilitate de reprezentare a strilor problemei ar fi printr-o list de perechi, o pereche fiind de forma [poziie, cifr], unde poziie este poziia pe tabl (de exemplu p1,...p9) iar cifr este cifra existent ntr-o anumit stare pe acea poziie. Astfel, starea final s-ar specifica n Prolog prin: final([[p1,1],[p2,2],[p3,3],[p4,8],[p5,bl],[p6,4],[p7,7],[p8,6],[p9,5]]) O astfel de reprezentare este mai intuitiv i mai convenabil pentru o afiare frumoas a soluiei dar este ineficient pentru prelucrare. Din acest motiv vom folosi o reprezentare diferit, respectiv o list ordonat de tipul: [poziie_blanc, poziie_cifra1, ..., poziie_cifra9]

de exemplu, pentru starea final, avem definiia:


final([5, 1, 2, 3, 6, 9, 8, 7, 4]). Pentru a putea testa problema pe mai multe stri iniiale, predicatul initial va avea un paramentru suplimentar, respectiv numrul unei stri iniiale. n continuare se definesc strile iniiale alternative, starea final, tranziiile de stri i un predicat de afiare "frumoas" a mozaicului.

% Definirea problemei mozaicului cu 8 cifre % Stare iniial 1 - merge cu orice strategie initial(s1, [1, 4, 2, 3, 6, 9, 8, 7, 5]). % Stare iniial 2 - merge cu orice strategie initial(s2, [3, 4, 1, 2, 6, 9, 8, 7, 5]). % Stare iniial 3 - merge cu parcurgere pe nivel i n adncime, dar nu cu bkt initial(s3, [5, 1, 6, 2, 3, 9, 8, 7, 4]). % Stare iniial 4 - merge cu parcurgere pe nivel, nu merge cu adncime i bkt initial(s4, [8, 4, 1, 3, 6, 9, 5, 7, 2]). % Stare iniial 4.1 - merge cu nivel i adncime, dar nu cu bkt initial(s5, [5, 1, 6, 2, 3, 9, 8, 7, 4]). % Stare iniial 6 - nu merge cu nici o strategie neinformat initial(s6, [9, 4, 2, 6, 8, 5, 1, 3, 7]). % Stare iniial 7 - nu merge cu nici o strategie neinformat initial(s7, [2, 4, 5, 6, 3, 9, 8, 7, 1]). % Stare final final([5, 1, 2, 3, 6, 9, 8, 7, 4]). % mut(+Tabla, -TablaNou, +Sens) - mut cifra care se poate muta n sensul Sens muta(Tabla, TablaN, Sens) :Tabla = [Poz | Rest], detlist(Sens, L), not member(Poz, L), calc(Poz, Poz1, Sens), repl(Poz1, Poz, Rest, Rest1), TablaN = [Poz1 | Rest1], !. % succ(+Tabl, -TablNou) - determin urmtoarea stare a tablei
123

succ(S, S1) :- (muta(S, S1, stg); muta(S, S1, dr); muta(S, S1, sus); muta(S, S1, jos)). % detlist determin poziiile din care nu se poate muta n Sens detlist(stg, [1, 4, 7]) :- !. detlist(dr, [3, 6, 9]) :- !. detlist(sus, [1, 2, 3]) :- !. detlist(jos, [7, 8, 9]). % calc(+Poz, -Poz1, +Sens) - calculeaz noua poziie dup mutare n Sens calc(Poz, Poz1, stg) :- !, Poz1 is Poz-1. calc(Poz, Poz1, dr) :- !, Poz1 is Poz+1. calc(Poz, Poz1, sus) :- !, Poz1 is Poz-3. calc(Poz, Poz1, jos) :- !, Poz1 is Poz+3. % repl - actualizeaz poziia pe tabl repl(Poz1, Poz, [Poz1 | Rest], [Poz | Rest]) :- !. repl(X, X1, [Y | Rest], [Y | Rest1]) :- repl(X, X1, Rest, Rest1). % Afiare poza(I) :pozitie(1, I, V1), pozitie(2, I, V2), pozitie(3, I, V3), pozitie(4, I, V4), pozitie(5, I, V5), pozitie(6, I, V6), pozitie(7, I, V7), pozitie(8, I, V8), pozitie(9, I, V9), nl, tab(10), write(V1), write(' '), write(V2), write(' '), write(V3), nl, tab(10), write(V4), write(' '), write(V5), write(' '), write(V6), nl, tab(10), write(V7), write(' '), write(V8), write(' '), write(V9), nl. pozitie(X, I, V) :- poz(X, I, 0, N), (N = 0, !, V = ' '; V = N). poz(X, [X | _ ], N, N) :- !. poz(X, [Y | Rest], N, NR) :- N1 is N+1, poz(X, Rest, N1, NR).
Predicatul poza(I) permite afisarea sub form de mozaic a unei stari. El poate fi folosit n afiarea soluiei definind predicatul scrie(S) astfel:

scrie(S) :- poza(S).
La cutarea n adncime se fac foarte multe mutri pn la starea final. Din aceast cauz nu se poate folosi pentru orice configuraie iniial afisarea cu predicatul poza (este generat eroarea Not enough stack) i este necesar pstrarea variantei iniiale de definire a lui scrie n care afiarea strilor se va face sub form de list, cu semnificaia definit n seciunea precedent. Soluia problemei, pentru o stare iniial, NrS, fixat dintre cele definite (s1, s2,...), se obine astfel:

solutie(NrS) :124

initial(NrS, Si), poza(Si), rezbreadth(Si). % rezdepth(Si). % rezbkt(Si).

12.4

Rezolvarea problemei mozaicului cu A*

Se pstreaz reprezentarea strilor problemei descris n seciunea precedent, se utilizeaz schema de rezolvare cu algoritmul A* prezentat n capitolul 11 i se definesc trei variante de predicate pentru calculul funciei euristice, conform celor trei funcii euristice discutate n seciunea 12.2. Funcia euristic h1 se calculeaz astfel: % euristic(+S, -H) - corespunztoare funciei h1 euristic(S, H) :- final(Sf), calc(S, Sf, 0, H). calc([], _ , HR, HR) :- !. calc(S, Sf, H, HR) :- S = [Poz | Rest], Sf = [Poz1 | Rest1], (Poz = Poz1, N = 0; Poz\ = Poz1, N = 1), H1 is H+N, calc(Rest, Rest1, H1, HR). Funcia euristic h2, distan Manhattan, se calculeaz astfel: % euristic(+S, +H) - corespunztoare funciei h2 euristic(S, H) :final(Sf), S = [ _ | Rest], Sf = [ _ | Rest1], calc(Rest, Rest1, 0, H). % calc(+R, +R1, +ValInit, -H) calc([], _ , H, H) :- !. calc(S, Sf, H, HR) :- S = [Poz | Rest], Sf = [Poz1 | Rest1], lincol(Poz, L, C), lincol(Poz1, L1, C1), (L> = L1, !, D1 is L-L1; D1 is L1-L), (C> = C1, !, D2 is C-C1; D2 is C1-C), Manh is D1+D2, H1 is H+Manh, calc(Rest, Rest1, H1, HR).
Pentru calculul funciei h3 se adug la h2 valoarea 3 * D(S), cu D calculat de predicatul dep(S,D).

% Daca se adaug la h2 si 3*D se obtine h3 e-adimisibila % euristic(+S, +H) - corespunztoare funciei h3 euristic(S,H):final(Sf), S = [ _ | Rest], Sf = [ _ | Rest1], calc(Rest, Rest1, 0, H1), dep(Rest,D), H is H1+ (3 * D). calc([], _ , H, H) :- !.
125

calc(S, Sf, H, HR) :- S = [Poz | Rest], Sf = [Poz1 | Rest1], lincol(Poz, L, C), lincol(Poz1, L1, C1), (L> = L1, !, D1 is L-L1; D1 is L1-L), (C> = C1, !, D2 is C-C1; D2 is C1-C), Manh is D1+D2, H1 is H+Manh, calc(Rest, Rest1, H1, HR). dep(S, D) :- dep(S, 0, D). dep([P], D, D). dep([P, P1 | Rest], D, DR) :(P = 5, !, D1 = 1; urm(P, U), (P1 = U, !, D1 = 0;D1 = 2)), D2 is D+D1, dep([P1 | Rest], D2, DR). % funcii ajuttoare div(X, Y, R) :- div(X, Y, 0, R). div(X, Y, N, N) :- (X = 0; X < Y), !. div(X, Y, N, NR) :- X > = Y, X1 is X-Y, N1 is N+1, div(X1, Y, N1, NR). % P mod 3 = 0 atunci Coloana = 3, Linie = P div 3 % P mod 3 \= 0 atunci Coloana = P mod 3, Linie = P div 3+1 lincol(P, L, C) :- T is P mod 3, (T = 0, !, C = 3, div(P, 3, L); C = T, div(P, 3, L1), L is L1+1). urm(1, 2). urm(2, 3). urm(3, 6). urm(4, 1). urm(6, 9). urm(9, 8). urm(8, 7). urm(7, 4). %% Stare initiala 1 initial(s1,[1,4,2,3,6,9,8,7,5]). %% Stare initiala 2 initial(s2,[3,4,1,2,6,9,8,7,5]). %% Stare initiala 3 initial(s3,[5,1,6,2,3,9,8,7,4]). %% Stare initiala 4 - merge cu h1, h2, h3 initial(s4,[8,4,1,3,6,9,5,7,2]). %% Stare initiala 5 - merge cu h2, h3, nu merge cu h1 initial(s5,[5,2,1,9,4,8,3,7,6]). %% Stare initiala 6 - merge numai cu h3 initial(s6,[9,4,2,6,8,5,1,3,7]). %% Stare initiala 7 - merge numai cu h3 initial(s7,[2,4,5,6,3,9,8,7,1]).

126

Rezolvarea problemei se obine astfel (atenie ! se alege numai una dintre cele trei definiii posibile pentru predicatul euristic, corespunztoare lui h1 sau h2 sau h3):

solutie(NrS):initial(NrS,Si), poza(Si), rezastar(Si).


Pentru exemplificarea performanelor strategiilor de cutare (neinformate i informate) discutate n acest capitol se prezint rezultatele obinute pentru apte stri iniiale n problema mozaicului cu opt cifre (D. s. nseamn depirea stivei), n figura 12.

127

Si

Poz

Strategii de cutare neinformate Nivel Adncime 30 mutri 28 apeluri BKT 30 mutri 28 apeluri

Cutare informat cu A* A* - h1 2 mutri 2 apeluri A* - h2 2 mutri 2 apeluri A* - h3 2 mutri 2 apeluri

Si 1 1 7 Si 2 2 1 7 Si 3 1 8 7 Si 4 2 1 7 Si 5 2 4 7 Si 6 6 1 8 Si 7 8 1 7

2 8 6 3 8 5 3 6 8 6

3 4 5

2 mutri 5 apeluri

4 mutri 4 5 4 2 5 3 4 5 5 mutri 57 apeluri 4 mutri 26 apeluri 15 apeluri

4 mutri 4 apeluri

4 mutri 4 apeluri

4 mutri 5 apeluri

4 mutri 4 apeluri

4 mutri 4 apeluri

30 mutri

Nu merge. D. s.

4 mutri 5 apeluri

4 mutri 4 apeluri

4 mutri 4 apeluri

D.s.

D. s.

5 mutri 6 apeluri

5 mutri 5 apeluri

5 mutri 5 apeluri

163 apeluri 338 apeluri

1 5 2 5 4

6 8 3 7 3

D. s.

D. s.

D. s.

D. s.

18 mutri

18 mutri

D. s. 74 apeluri

D. s. 163 apeluri

D. s.

D. s.

D. s.

23 mutri 124 apeluri

4 2 6 3 5

D. s. 74 apeluri

D. s. 163 apeluri

D. s.

D. s.

D. s.

22 mutri 84 apeluri

Figura 12. Performanele strategiilor de cutare n problema mozaicului

128

12.5

Exerciii propuse

EP1. S se implementeze problema misionarilor i canibalilor folosind un algoritm A* bazat pe funcia euristic h2. EP2. S se extind problema misionarilor i a canibalilor pentru N misionari, N canibali i capacitatea brcii de M persoane. S se rezolve aceast problem prin strategii de cutare neinformate i informate i s se fac o analiz comparativ a performanelor metodelor, de tipul celei din figura 12. EP3. S se foloseasc schemele generale de cutare pentru rezolvarea problemei maimuei i a bananei prezentat n capitolul 4. EP4. S se foloseasc cutri informate i neinformate pentru rezolvarea problemei ranului (10.1); se va defini o funcie euristic adecvat. Se va propune, de asemenea, o reprezentare a strilor problemei alternativ celei din seciunea 10.1. EP5. S se modifice implementrile rezolvrii problemei mozaicului astfel nct s se afieze statisticile prezentate n figura 12. EP6. S se aplice algoritmul A* cu costuri explicite (dezvoltat n cadrul exerciiului propus 4 din capitolul 11) pentru rezolvarea problemei comis-voiajorului.

13 Strategii de cutare aplicate la jocuri


n aceast seciune se discut strategii de cutare specifice unui joc ce implic doi adversari. Strategia de cutare necesar n acest caz este mai complicat datorit existenei adversarului ostil i imprevizibil n mutri. Exist dou tipuri de jocuri: jocuri n care spaiul de cutare poate fi investigat exhaustiv i jocuri n care spaiul de cutare nu poate fi investigat complet deoarece este prea mare.

13.1

Algoritmul Minimax pentru spaii de cutare investigate exhaustiv

n procesul de cutare trebuie luate n considerare micrile posibile ale adversarului. Se presupune c oponentul are acelai cunotine despre spaiul de cutare ca i juctorul i c folosete aceste cunotine pentru a ctiga. Algoritmul minimax implementeaz o strategie de cutare bazat pe aceast presupunere. n prezentarea algoritmului, ne vom situa pe poziia unuia din cei doi participani, ales arbitrar i numit juctor, cellat participant fiind numit adversar sau oponent.
Juctorul este referit ca MAX iar adversarul su ca MIN. MAX ncearc s ctige maximizndu-i avantajul, iar MIN ncearc s ctige minimiznd avantajul lui MAX. Se presupune c MIN va muta ntotdeauna n configuraia care este cea mai defavorabil pentru MAX n acel moment. Pe parcursul jocului se genereaz spaiul de cutare ce conine ca noduri configuraiile posibile ale jocului iar ca tranziii ntre acestea mutrile posibil de executat. Pentru implementarea cutrii se eticheteaz fiecare nivel din spaiul de cutare cu MAX sau cu MIN, dup cum micarea este fcut de juctor sau de adversar. Se genereaz tot spaiul de cutare i fiecare nod terminal este 129

evaluat la scorul configuraiei terminale corespunztoare; de exemplu 1 dac este o configuraie ctigtoare pentru MAX i 0 dac este nectigtoare (este ctigtoare pentru MIN). Se pot asocia i valori (pozitive) de cstig pentru juctor n fiecare nod terminal, corespunztoare punctajului obinut de acesta la sfritul jocului. Apoi se propag aceste valori n sus n graf, la nodurile prini, dup urmtoarele reguli: 1. dac nodul printe este MAX atunci i se atribuie valoarea maxim a succesorilor si; 2. dac nodul printe este MIN atunci i se atribuie valoarea minim a succesorilor si. Valoarea asociat astfel fiecrei stri descrie cea mai bun valoare pe care juctorul poate spera s o ating (presupunnd c oponentul joac conform algoritmului minimax). Valorile obinute sunt folosite pentru a alege ntre diverse mutri posibile. Un exemplu de spaiu de cutare Minimax este prezentat n figura 13.

MAX

A/3

MIN

B/3

C/2

D/2

MAX

E/3

F / 12

G/8

H/2

I/4

J/6

K / 14

L/5

M/2

Figura 13. Un posibil spaiu de cutare Minimax


Fie urmtorul joc (NIM): Mai multe bee se pun pe mas ntr-o grmad iniial. Fiecare participant trebuie s mpart cte o grmad n dou grmezi cu un numr diferit de bee. De exemplu, dac iniial sunt 6 bee, acestea pot fi mprite 5 - 1 sau 4 - 2, dar nu 3 - 3. Primul juctor care nu mai poate face nici o micare pierde. Spaiul de cutare pentru configuraia iniial de 7 bee este prezentat n figura 14.

MIN

7/1

MAX

6-1/1

5-2/1

4-3/1

MIN

5-1-1/0

4-2-1/1

3-2-2/0

3-3-1/1

MAX

4-1-1-1/0

3-2-1-1/1

2-2-2-1/0

MIN

3-1-1-1-1/0

2-2-1-1-1/1

MAX

2-1-1-1-1-1/0

130

Figura 14. Spaiul de cutare Minimax pentru Nim cu 7 bee


Se observ c dac ncepe MIN, juctorul MAX poate ctiga ntotdeauna jocul, deoarece orice micare iniial a lui MIN duce ntr-o stare din care MAX poate ctiga dac joac bine.

13.2

Algoritmul Minimax cu adncime de cutare finit

Dac nu se poate investiga exhaustiv spaiul de cutare, se aplic algoritmul minimax. pn la o adncime finit, arbitrar prestabilit. Configuraiile aflate la aceast adncime sunt considerate stri terminale. Deoarece ele nu sunt (de cele mai multe ori) stri terminale, deci de sfrit de joc, punctajul asociat acestora se estimeaz pe baza unei funcii euristice care indic ct de promitoare este o astfel de stare pentru ctigul juctorului.
De pe fiecare nivel curent se caut pe urmtoarele n nivele ale spaiului. Valoarea care se atribuie unui nod n urma investigrii a n nivele sub el i folosind funcia euristic nu este o indicaie dac se ctig sau se pierde, ci o estimare euristic a celei mai bune stri la care se poate ajunge n n micri de la nodul dat. Descrierea algoritmului n aceast abordare este:

Descriere Minimax( S ) pentru fiecare succesor Sj al lui S (obinut printr-o mutare opj) execut val( Sj ) Minimax( Sj ) aplic opj pentru care val( Sj ) este maxim sfrit Minimax( S ) { ntoarce o estimare a strii S } dac nivel( S ) = n atunci ntoarce eval( S ) altfel dac MAX mut n S atunci pentru fiecare succesor Sj al lui S execut val( Sj ) Minimax( Sj ) ntoarce max( val( Sj ), j ) altfel { MIN mut n S } pentru fiecare succesor Sj al lui S execut val( Sj ) Minimax( Sj ) ntoarce min( val( Sj ), j ) sfrit
Pentru a da un exemplu de funcie de estimare, s considerm jocul de Tic-Tac-Toe (X i O). Alegem o funcie de estimare euristic eval( S ) ce ncearc s msoare conflictul existent n joc n starea S. Valorea eval( S ) este numrul total posibil de linii ctigtoare ale lui MAX n starea S din care se scade numrul total posibil de linii ctigtoare ale lui MIN n starea S. Prin linie ctigtoare se nelege o linie orizontal, vertical sau diagonal care a fost, este doar parial sau poate fi completat numai cu X sau numai cu O. Dac S este o stare din care MAX poate face o micare cu care ctig, atunci eval( S ) = (o valoare foarte mare), iar dac S este o stare din care MIN poate ctiga cu o singur mutare, atunci eval( S ) = - (o valoare foarte mic). Un exemplu este prezentat n figura 15.

131

X O

X are 6 linii ctigtoare posibile O are 5 linii ctigtoare posibile eval( S ) = 6 - 5 = 1

Figura 15. Funcia euristic de evaluare a unei stri n jocul Tic-Tac-Toe


Dezavantajul abordrii cu adncime de cutare finit egal cu n este acela c poate s apar efectul de orizont, adic la nivelul n se neglijeaz ci care la nivelul (n + 1) ar putea fi mai bune. Efectul de orizont poate fi parial nlturat dac se investigheaz nainte anumite stri care par a fi foarte promitoare. Astfel, anumite stri cheie (de exemplu captura unei piese) se investigheaz la nivele suplimentare. De obicei se pune o limit de timp i se investigheaz iterativ nivele din ce n ce mai mari pn cnd se atinge limita de timp impus. Prezentm n continuare implementarea n Prolog a acestui algoritm i aplicarea lui la jocul de Tic-Tac-Toe (X i O):

% Implementarea algoritmului Minimax % Partea independent de jocul particular % Predicatul minimax: minimax(+Poz, -CelMaiBun, -Val) % Poz este poziia considerat, Val este valoarea sa minimax. % Cea mai bun mutare este ntoars n CelMaiBun minimax(Poz, CelMaiBun, Val) :% PozList = lista mutarilor posibile din pozitia Poz mutari(Poz, [Prim | PozList]), !, minimax(Prim, _ , VPrim), bun(Poz, PozList, Prim, VPrim, CelMaiBun, Val). minimax(Poz, _ , Val) :- valstatic(Poz, Val). % Poz nu are succesori bun(Curenta, [], Poz, Val, Poz, Val) :- !. bun(Curenta, [Poz | PozList], PozCurenta, ValCurenta, PozBuna, ValBuna) :minimax(Poz, _ , Val), bun_dela(Curenta, Poz, Val, PozCurenta, ValCurenta, PozNoua, ValNoua), bun(Curenta, PozList, PozNoua, ValNoua, PozBuna, ValBuna). bun_dela(Curenta, Poz0, Val0, Poz1, Val1, Poz0, Val0) :muta_min(Curenta), Val0 < Val1, !. bun_dela(Curenta, Poz0, Val0, Poz1, Val1, Poz0, Val0) :muta_max(Curenta), Val0 > Val1, !. bun_dela(Curenta, Poz0, Val0, Poz1, Val1, Poz1, Val1). % Jocul X i 0 % O poziie se memoreaz astfel: pos(x, [x, b, 0, b....], 3) unde % x = juctorul care mut curent
132

% [...] este ceea ce se gsete n tabl, pe linii % 3 reprezint cte mutari se mai fac n adncime, maxim, n minimax % Generatorul de mutri posibile mutari(Poz, PozList) :- bagof(P, mutare(Poz, P), PozList). mutare(pos(P, Tabla, N), pos(O, Tabla1, N1)) :N > 0, not(final(Tabla, _ )), opus(P, O), N1 is N - 1, subst(Tabla, P, Tabla1). opus(x, 0). opus(0, x). subst([b | Rest], P, [P | Rest]). subst([X | Rest], P, [X | Rest1]) :- subst(Rest, P, Rest1). % Evaluarea static valstatic(pos( _ , B, N), Val) :- final(B, P), !, castigator(P, N, Val). valstatic(pos( _ , Tabla, _ ), Val) :lin1(Tabla, L1), lin2(Tabla, L2), lin3(Tabla, L3), col1(Tabla, C1), col2(Tabla, C2), col3(Tabla, C3), dia1(Tabla, D1), dia2(Tabla, D2), Val is L1 + L2 + L3 + C1 + C2 + C3 + D1 + D2. final([P, P, P | _ ], P) :- P \= b, !. final([ _ , _ , _ , P, P, P | _ ], P) :- P \= b, !. final([ _ , _ , _ , _ , _ , _ , P, P, P], P) :- P \= b, !. final([ P, _ , _ , P, _ , _ , P | _ ], P) :- P \= b, !. final([ _ , P, _ , _ , P, _ , _ , P | _ ], P) :- P \= b, !. final([ _ , _ , P, _ , _ , P, _ , _ , P], P) :- P \= b, !. final([P, _ , _ , _ , P, _ , _ , _ , P], P) :- P \= b, !. final([ _ , _ , P, _ , P, _ , P | _ ], P) :- P \= b, !. final(Tabla, b) :- not(member(b, Tabla)). castigator(x, N, Val) :- !, Val is -10 * (N + 1). castigator(0, N, Val) :- !, Val is 10 * (N + 1). castigator( _ , _ , 0). lin1([b, b, b | _ ], 1) :- !. lin1( _ , 0). lin2([_ , _ , _ , b, b, b | _ ], 1) :- !. lin2( _ , 0). lin3([ _ , _ , _ , _ , _ , _ , b, b, b], 1) :- !.
133

lin3( _ , 0). col1([ b, _ , _ , b, _ , _ , b | _ ], 1) :- !. col1( _ , 0). col2([ _ , b, _ , _ , b, _ , _ , b | _ ], 1) :- !. col2( _ , 0). col3([ _ , _ , b, _ , _ , b, _ , _ , b], 1) :- !. col3( _ , 0). dia1([ b, _ , _ , _ , b, _ , _ , _ , b], 1) :- !. dia1( _ , 0). dia2([ _ , _ , b, _ , b, _ , b | _ ], 1) :- !. dia2( _ , 0). %Tipul nodului (min sau max) muta_min(pos(x, _ , _ )). muta_max(pos(0, _ , _ )). % Jocul efectiv run :primul(P), adancime(N), iniial(Tabla), scrie_tabla(Tabla), joc(pos(P, Tabla, N)). primul(P) :write($Incepe un nou joc...$), nl, write($Eu sunt cu 0, tu esti cu x$), nl, write($Cine incepe (x/0)? $), read(P). adancime(N) :- write($Numarul de semi-mutari adancime? $), read(N). iniial([b, b, b, b, b, b, b, b, b]). joc(pos(P, Tabla, _ )) :- final(Tabla, P1), !, scrie_castig(P1). joc(pos(x, Tabla, N)) :- !, det_mutare(Tabla, Succ), scrie_tabla(Succ), joc(pos(0, Succ, N)). joc(pos(0, Tabla, N)) :minimax(pos(0, Tabla, N), pos(x, Succ, _ ), Val), scrie_mutare(Succ, Val), joc(pos(x, Succ, N)). scrie_castig(b) :- !, write($Remiza.$), nl.
134

scrie_castig(x) :- !, write($Ai castigat.$), nl. scrie_castig(_ ) :- write($Ai pierdut.$), nl. det_mutare(Tabla, Succ) :repeat, det_coord(L, C), N is L * 3 + C, verifica(N, Tabla, Succ), !. scrie_mutare(Tabla, Val) :write($Mutarea mea este: $), nl, scrie_tabla(Tabla), write($Punctaj: $), write(Val), nl, anunta(Val). anunta(10) :- !, write($Acum sunt sigur ca voi castiga.$), nl. anunta(_ ). det_coord(L, C) :write($Linia si coloana? $), read(L1), read(C1), L is L1 - 1, C is C1 - 1. verifica(0, [b | Rest], [x | Rest]) :- !. verifica(N, [X | Tabla], [X | Succ]) :- N1 is N - 1, verifica(N1, Tabla, Succ). scrie_tabla(Tabla) :repl(Tabla, ' ', [E1, E2, E3, E4, E5, E6, E7, E8, E9]), write($ 1 2 3$), nl, write($1 $), write(E1), write($ $), write(E2), write($ $), write(E3), nl, write($2 $), write(E4), write($ $), write(E5), write($ $), write(E6), nl, write($3 $), write(E7), write($ $), write(E8), write($ $), write(E9), nl. % Predicate de uz general repeat. repeat :- repeat. member(X, [X | _ ]). member(X, [_ | Rest]) :- member(X, Rest). repl([], _ , []) :- !. repl([b | Rest], X, [X | Rest1]) :- !, repl(Rest, X, Rest1). repl([E | Rest], X, [E | Rest1]) :- repl(Rest, X, Rest1).

135

13.3

Algoritmul tierii alfa-beta

Este posibil s se obin decizia corect a algoritmului Minimax fr a mai inspecta toate nodurile din spaiului de cutare pn la un anumit nivel. Procesul de eliminare a unei ramuri din arborele de cutare se numete tiere (pruning).
Pentru a ilustra ideea acestui algoritm, se consider spaiul de cutare din figura 13, redesenat n figura 16.

MAX

A/3

MIN

B/3

C/2

D/2

MAX

E/3

F / 12

G/8

H/2

I/4

J/6

K / 14

L/5

M/2

Figura 16. Tierea alfa-beta a spaiului de cutare Se observ c, odat gsit succesorul H cu valoarea 2 al nodului C, nu mai este necesar inspectarea celorlai succesori ai nodului C, deoarece: Nodul C este pe un nivel de MIN i va primi o valoare mai mic sau egal cu 2; Pentru nodul A, care este pe un nivel de MAX, am gsit anterior succesorul B cu valoarea 3, deci A va avea o valoare mai mare sau egal cu 3, ceea ce nseamn c nodul C devine un succesor neinteresant A (valoarea lui C este mai mic dect 3).
Fie cea mai bun valoare (cea mai mare) gsit pentru MAX i cea mai bun valoare (cea mai mic) gsit pentru MIN. Algoritmul alfa-beta actualizeaz i pe parcursul parcurgerii arborelui i elimin investigrile subarborilor pentru care sau sunt mai proaste. Terminarea cutrii (tierea unei ramuri) se face dup dou reguli: 1. Cutarea se oprete sub orice nod MIN cu o valoare mai mic sau egal cu valoarea a oricruia dintre nodurile MAX predecesoare nodului MIN n cauz. 2. Cutarea se oprete sub orice nod MAX cu o valoare mai mare sau egal cu valoarea a oricruia dintre nodurile MIN predecesoare nodului MAX n cauz. Deci algoritmul exprim o relaie ntre nodurile de pe nivelul n i nodurile de pe nivelul (n + 2). Dac juctorul MAX are posibilitatea s ajung la un nod m care este mai bun dect nodul curent de pe nivelul n, atunci ntr-un joc real, nodul de pe nivelul n nu poate fi niciodat considerat, aa cum se vede din figura 17. Se observ c

A are = 3 (A este pe un nivel MIN, deci val( A ) nu poate fi mai mare ca 3) B este tiat deoarece 5 > 3 C are = 3 (C este pe un nivel MAX, deci val( C ) nu va fi mai mic dect 3) D este tiat deoarece O < 3 E este tiat deoarece 2 < 3, deci val( C ) este 3.
136

MAX

C/3

MIN

A/3

D/0

E/2

MAX

F/3

B/3

G/0

H/2

MIN

I/2

J/3

K/5

L/0

M/2

N/1

Figura 17. Eliminarea investigrii unor noduri n algoritmul alfa-beta


Algoritmul alfa-beta const n dou proceduri: MIN i MAX. Algoritmul ncepe fie prin apelarea procedurii MAX, dac juctorul mut primul, fie prin apelarea procedurii MIN, dac adversarul mut primul, iniial fiind foarte mic (0) i foarte mare ().

MAX(S, , ) altfel

{ ntoarce valoarea maxim a unei stri. }

dac nivel( S ) = n atunci ntoarce eval( S ) pentru fiecare succesor Sj al lui S execut max(, MIN(Sj, , )) dac atunci ntoarce ntoarce sfrit MIN(S, , ) altfel pentru fiecare succesor Sj al lui S execut min(, MAX(Sj, , )) dac atunci ntoarce ntoarce sfrit
Algoritmul alfa-beta nu elimin limitrile de la Minimax, cum ar fi efectul de orizont. Avantajul su este ns acela c reduce numrul de noduri inspectate, permind o cretere a nivelului de adncime n investigarea nodurilor urmtoare, n. Algoritmul devine eficient dac se face o ordonare a succesorilor n inspectare: cele mai bune micri ale lui MAX, respectiv ale lui MIN, din punctul de vedere al lui MAX nti. De exemplu, pentru jocul de ah, unde factorul de ramificare este aproximativ egal cu 35, utilizarea algoritmului alfa-beta i o ordonare adecvat a strilor inspectate poate reduce factorul de ramificare la 6, ceea ce permite dublarea adncimii de cutare, permind trecerea de la un nivel de novice la un nivel de expert. 137

{ ntoarce valoarea minim a unei stri. }

dac nivel( S ) = n atunci ntoarce eval( S )

Prezentm n continuare implementarea algoritmului alfa-beta n Prolog i aplicarea lui n jocul de Tic-Tac-Toe (X i O):

% Implementarea algoritmului alfa-beta % Partea independent de jocul particular jucat alfabeta(Poz, Alfa, Beta, SucBun, Val) :mutari(Poz, [P1 | LPoz]), !, alfabeta(P1, Alfa, Beta, _ , V1), nou(Alfa, Beta, P1, V1, NAlfa, NBeta), bunsucc(LPoz, NAlfa, NBeta, P1, V1, SucBun, Val). alfabeta(Poz, Alfa, Beta, SucBun, Val) :- valstatic(Poz, Val). bunsucc([], _ , _ , P1, V1, P1, V1) :- !. bunsucc([Poz | LPoz], Alfa, Beta, Poz1, Val1, PozBuna, ValBuna) :alfabeta(Poz, Alfa, Beta, _ , Val), bun_dela(Poz, Val, Poz1, Val1, Poz2, Val2), destuldebun(LPoz, Alfa, Beta, Poz2, Val2, PozBuna, ValBuna). destuldebun(_ , Alfa, Beta, Poz, Val, Poz, Val) :muta_min(Poz), Val > Beta, !; muta_max(Poz), Val < Alfa, !. destuldebun(LPoz, Alfa, Beta, P, Val, PozBuna, ValBuna) :nou(Alfa, Beta, P, Val, NAlfa, NBeta), bunsucc(LPoz, NAlfa, NBeta, P, Val, PozBuna, ValBuna). nou(Alfa, Beta, Poz, Val, Val, Beta) :- muta_min(Poz), Val > Alfa, !. nou(Alfa, Beta, Poz, Val, Alfa, Val) :- muta_max(Poz), Val < Beta, !. nou(Alfa, Beta, _ , _ , Alfa, Beta). bun_dela(Poz, Val, Poz1, Val1, Poz, Val) :muta_min(Poz), Val > Val1, !; muta_max(Poz), Val < Val1, !. bun_dela(_ , _ , Poz1, Val1, Poz1, Val1).
Pentru a aplica algoritmul alfa-beta la jocul Tic-Tac-Toe, singura modificare fa de implementarea prezentat n seciunea anterioar este urmtoarea:

% Jocul efectiv run :primul(P), adancime(N), iniial(Tabla), scrie_tabla(Tabla), joc(pos(P, Tabla, N)).

138

joc(pos(P, Tabla, _ )) :- final(Tabla, P1), !, scrie_castig(P1). joc(pos(x, Tabla, N)) :- !, det_mutare(Tabla, Succ), scrie_tabla(Succ), joc(pos(0, Succ, N)). joc(pos(0, Tabla, N)) :alfabeta(pos(0, Tabla, N), -100, 100, pos(x, Succ, _ ), Val), scrie_mutare(Succ, Val), joc(pos(x, Succ, N)). anunta(X) :- X >= 10, !, write($Acum sint sigur ca voi cistiga.$), nl. anunta( _ ).

13.4

Exerciii propuse

EP1. S se implementeze algoritmul alfa-beta pentru jocul NIM, pentru diferite numere de bee ale configuraiei iniiale, att cu investigarea exhaustiv a spaiului de cutare (atunci cnd este posibil) ct i cu impunerea unei limite n cutare. EP2. EP3. joc. S se aplice algoritmul alfa-beta pentru jocul NIM. Implementai jocul dumneavoastr preferat, alegnd cea mai potrivit strategie de

14 Descompunerea problemelor n subprobleme


Rezolvarea unor probleme poate fi convenabil realizat prin descompunerea alternativ a problemei n subprobleme care, la rndul lor, sunt descompuse n alte subprobleme pn la depistarea unor probleme a cror rezolvare este imediat, probleme numite elementare. Reprezentarea unui astfel de proces poate fi descris prin grafuri sau arbori i/Sau [Flo83].

14.1

Arbori i/Sau

n continuare se va prezenta cutarea n arbori i/Sau pentru anumite cazuri simple. ntr-o prim variant se specific compact ntreg arborele i/Sau al posibilelor rezolvri ale problemei i se indic cum poate fi gsit o soluie n aceast reprezentare. Un nod Sau este reprezentat printr-o structur Prolog sau(NumeProb, ListSuccesori) unde ListSuccesori reprezint nodurile (subproblemele) n care se poate descompune alternativ problema NumeProb. Un nod i este reprezentat printr-o structur Prolog si(NumeNod, ListaSubprob) unde NumeNod este un nume convenional asociat nodului i iar ListSubprob este lista subproblemelor ce trebuie rezolvate pentru a rezolva problema iniial. Un nod elementar (cu rezolvare imediat) este marcat de structura Prolog elementar(NumeProb) iar un nod terminal neelementar (care nu mai poate fi descompus) prin neelementar(NumeProb), unde NumeProb este numele subproblemei corespunztoare. O soluie a problemei este un subarbore n care nodul problem iniial este nod rezolvat. n soluie, nodurile problem elementar vor fi marcate prin structura frunz(NumeNod) iar un nod Sau va avea un unic succesor, respectiv nodul i ales pentru descompunere, marcat prin structura succ(NumeProb, ArboreSolutie).

% Prima variant Prolog % Descrierea compact a spaiului de cutare arbore i/Sau: descriere(sau(a, [si(b, [elementar(d), sau(e, [elementar(h)])]), si(c, [sau(f, [elementar(h), neelementar(i)]), elementar(g)])])).
139

% Rezolvarea problemei % n frunze sunt puse problemele elementare ele fiind considerate rezolvate. rezolv(elementar(Problem), frunza(Problem)). % Un nod Sau este rezolvat dac cel puin unul din succesorii si este rezolvat. rezolv(sau(Problem, Succesori), succ(Problem, ArboreSolutie)) :member(S, Succesori), rezolv(S, ArboreSolutie). % Un nod i este rezolvat dac toi succesorii si sunt rezolvai. rezolv(si(Problem, Succesori), si(Problema, ArboriSolutie)) :rezolv_toti(Succesori, ArboriSolutie). rezolv_toti([], []). rezolv_toti([Problema | Rest], [Arbore | Arbori]) :rezolv(Problema, Arbore), rezolv_toti(Rest, Arbori). % Soluia problemei solutie(ArboreSolutie) :- descriere(Problema), rezolv(Problema, ArboreSolutie).
ntr-o a doua variant de reprezentare, nodurile i, Sau i nodurile asociate problemelor elementare sunt descrise prin predicatele:

sau(NumeProb, NumeSuccSi) - indic o posibil descompunere a problemei NumeProb ntr-o mulime de subprobleme reprezentat prin nodul i NumeSuccSi; exist cte un fapt Prolog pentru fiecare descompunere alternativ posibil; si(NumeSi, ListaSucc) - indic un nod i cu numele NumeSi corespunztor subproblemelor indicate in lista ListaSucc; NumeSi trebuie s apar ntr-un predicat sau; elementar(NumeProb) - indic o problem elementar, cu rezolvare imediat.
Nodurile terminale neelementare vor fi specificate implicit prin faptul c ele nu apar nici ntr-un predicat elementar nici ntr-un predicat sau, deci nu au descompunere. Reprezentarea Prolog a soluiei va fi aceeai ca n prima variant.

% A doua variant Prolog % Descrierea spaiului de cutare arbore i/Sau: sau(a,b). sau(a,c). si(b,[d,e]). elementar(d). sau(e,h). elementar(h). si(c,[f,g]). sau(f,h). sau(f,i). neelementar(i). elementar(g). % Rezolvarea problemei rezolv1(Problema, frunza(Problema)) :- elementar(Problema). rezolv1(Problema, succ(Problema, ArboreSolutie)) :sau(Problema, S), rezolv1(S, ArboreSolutie). rezolv1(Problema, si(Problema, ArboriSolutie)) :si(Problema, Succesori), rezolv_toti1(Succesori, ArboriSolutie). rezolv_toti1([],[]).
140

rezolv_toti1([Problema | Rest], [Arbore | Arbori]) :rezolv1(Problema, Arbore), rezolv_toti1(Rest, Arbori).

14.2

Cutarea n cazul regulilor de producie

Rezolvarea problemelor ntr-un sistem bazat pe reguli de producie cu nlnuirea napoi a regulilor (backward chaining) [LS93, Flo93] poate fi convenabil reprezentat printr-un spaiu de cutare de tip arbore i/Sau. Regulile care concluzioneaz despre o anumit valoare de atribut reprezint descompuneri alternative ale problemei ce const n determinarea valorii acelui atribut iar ipotezele regulilor reprezint subproblemele ce trebuie rezolvate pentru a putea obine concluzia regulii. n continuare rafinm cutarea soluiei ntr-un spaiu de cutare i/Sau prezentat n seciunea anterioar pentru ca apoi s aplicm aceast rezolvarea pentru construirea unui microsistem bazat pe reguli de producie, cu nlnuirea napoi a regulilor. n programul ce urmez vom face urmtoarele modificri fa de rezolvarea anterioar:

spaiul de cutare este graf i nu arbore; pentru aceasta trebuie meninut lista OPEN n scopul testrii nodurilor anterior parcurse; se reamintete c n OPEN se memoreaz numai noduri problem (deci numele nodurilor sau a problemelor elementare) i nu se memoreaz nodurile i; rezolvarea pemite afiarea explicaiilor, deci a modului de soluionare a problemei, prin afiarea arborelui soluie gsit (acesta are aceeai form ca n seciunea anterioar); aceast facilitate este esenial n cazul sistemelor bazate pe reguli de producie; nodurile rezolvate, respectiv cele nerezolvabile, se marchez ca atare prin adugarea (cu assert) a unui predicat corespunztor, n acest fel evitnd investigarea multipl a aceleiai probleme.
Pentru nceput se consider spiul de cutare prezentat n figura 18 i descris n programul ce urmeaz. n figur, toate nodurile terminale sunt considerate elementare, deci cu rezolvare imediat, un arbore soluie posibil fiind pus n eviden de sgeile ngroate. Dac se elimin, de exemplu, faptele elem(f) i elem(i), nodurile f i i sunt implicit considerate de program ca noduri terminale neelementare iar arborele marcat ngroat devine singurul arbore soluie pentru problema iniial a.

141

Figura 18. Spaiu de cutare i/Sau % Descrierea spaiului de cutare

i1

i2

i3

i4

i3

i5

% Un arbore soluie posibil: succ(a, si(si1, [succ(b, si(si4, [elem(h)])), elem(c)])). % De ncercat i cu f i g netrecui ca elementari; % apoi i cu h nlocuit cu a; apoi i cu i neelementar. sau(a, si1). sau(a, si2). si(si1, [b, c]). si(si2, [d, e]). sau(b, si3). sau(b, si4). si(si3, [f, g]). si(si4, [a]). sau(d, si3). sau(d, si5). si(si5, [i]). elem(c). elem(e). elem(f). elem(g). elem(h). elem(i). rezolv(P) :rezolv(P, ArbSol, []), ( rezolvat(P, _ ), write(P), nl, write('Explicatii? (da/nu) '), read(X), (X = da, afis(ArbSol) ; X = nu) ; nerezolvat(P), write('Nu exista solutie')). rezolv(P, ArbSol, _ ) :- rezolvat(P, ArbSol), !. rezolv(P, ArbSol, _ ) :- elem(P), ArbSol = fr(P), marchez_rezolvat(P, ArbSol). rezolv(P, ArbSol, Open) :sau(P, S1), not member(P, Open), rezolv(S1, ArbSol1, [P | Open]), rezolvat(S1, _ ), !, ArbSol = succ(P, ArbSol1), marchez_rezolvat(P, ArbSol). rezolv(P, ArbSol, Open) :- si(P, Succesori), rezolv_toti(Succesori, ArboriSol, Open), toti_rezolvati(Succesori), !, ArbSol = si(P, ArboriSol), marchez_rezolvat(P, ArbSol). rezolv(P, _ , _ ) :- marchez_nerezolvat(P).
142

rezolv_toti([], [], _ ). rezolv_toti([P | Rest], [Arb | RestArb], Open) :rezolv(P, Arb, Open), rezolv_toti(Rest, RestArb, Open). toti_rezolvati([]). toti_rezolvati([P | Rest]) :- rezolvat(P, _ ), toti_rezolvati(Rest). marchez_rezolvat(P, ArbSol) :- assert(rezolvat(P, ArbSol)). marchez_nerezolvat(P) :- assert(nerezolvat(P)). afis(fr(P)) :- write('Frunza '), write(P). afis(si(P, LSuc)) :nl, write('Nod SI '), write(P), nl, write(' cu succesori '), writelis(LSuc). afis(succ(P, S)) :- nl, write('Nod SAU '), write(P), write(' cu succesor '), afis(S). writelis([]). writelis([El | Rest]) :- afi(El), write(' '), writelis(Rest). member(X, [X | _ ]) :- !. member(X, [ _ | Rest]) :- member(X, Rest). % Se apeleaza init dup fiecare rezolvare sau nainte de o nou rezolvare % pentru a terge din baza de date Prolog ceea ce a fost marcat anterior. init :- retract(rezolvat(X, Y)), fail. init :- retract(nerezolvat(X)), fail. init.
Predicatul init are ca scop iniializarea memoriei de lucru a sistemului, deci eliminarea marcajelor de rezolvat sau nerezolvat pentru diferite noduri nainte de o nou consultaie. Considerm acum aplicarea programului n cazul urmtorului set de reguli de producie:

% r5:dac mamifer = da i carnivor = da i dungi = da % atunci tip = tigru % r6: dac mamifer = da i carnivor = da i pete = da % atunci tip = leopard % r3: dac mnnc_carne = da % atunci carnivor = da % r4:dac dini_ascuii = da i flci = da % atunci carnivor = da % r1: dac are_pr = da % atunci mamifer = da
143

% r2:dac d_lapte = da % atunci mamifer = da


Propunem urmtoarea reprezentare a acestor reguli n Prolog. Trebuie menionat c exist i alte posibiliti de reprezentare, de exemplu prin definirea unor operatori Prolog pentru dac, atunci, etc. Exerciiile acestui capitol vor cere realizarea unor reprezentri alternative a regulilor. Spre deosebire de spaiul de cutare anterior, unde problemele elementare erau predefinite, n cazul regulilor de producie un nod terminal este un atribut care nu apare n concluzia nici unei reguli. Neexistnd nici o regul care s concluzioneze despre un atribut nu exist o descompunere a problemei gsirii valorii acelui atribut n subprobleme. Pentru un astfel de nod, sistemul trebuie s ntrebe utilizatorul valoarea acelui nod. Dac utilizatorul indic ca valoare a atributului o valoare ce apare in ipoteza unei reguli, nodul corespunztor devine rezolvat. n caz contrar, nodul corespunztor devine nerezolvabil i programul trebuie s gseasc soluii alternative. ntrebarea ctre utilizator este pus de predicatul elem([A,R]) care obine pentru atributul A valoarea R indicat de utilizator. Pentru setul de reguli definite sistemul se lanseaz prin apelul predicatului rezolv([tip,X]), unde predicatul rezolv este cel definit anterior.

sau([tip, tigru], r5). sau([tip, leopard], r6). si(r5, [[mamifer, da], [carnivor, da], [dungi, da]]). si(r6, [[mamifer, da], [carnivor, da], [pete, da]]). sau([carnivor, da], r3). sau([carnivor, da], r4). si(r3, [[mananca_carne, da]]). si(r4, [[dinti_ascutiti, da], [falci, da]]). sau([mamifer, da], r1). sau([mamifer, da], r2). si(r1, [[are_par, da]]). si(r2, [[da_lapte, da]]). elem([A, R]) :- not sau([A, R], _ ), write(A), write('? = '), read(Raspuns), Raspuns = R.

14.3
EP1.

Exerciii propuse
Folosind operatorii predefinii: :- op(600, xfx, ---> ). > :- op(500, xfx, : ).

nodurile se pot descrie prin clauze de forma: a ---> or : [b, c]. > b ---> and :[d, e]. > % nod sau % nod i

S se rescrie predicatele pentru determinarea soluiei n aceast reprezentare.

144

EP2. S se defineasc operatori Prolog, n maniera celor definii n seciunea 8.3, pentru a putea exprima regulile de producie din sistem direct sub form de fapte Prolog n forma iniial, de exemplu: r5: dac mamifer egal da si carnivor egal da si dungi egal da atunci tip egal tigru. n aceste condiii s se rescrie predicatele de rezolvare. EP3. Care este funcionarea programelor din seciunile 14.1 i 14.2 n cazul n care exist mai multe soluii. Comentai i facei modificri. EP4. S se adauge sistemului posibilitatea de rspuns la ntrebri de tipul "de ce?" (de ce ? sistemul are nevoie de o anumit valoare) prin afiarea regulii curente a crei satisfacere se ncearc, i la ntrebri de tipul "cum?" (cum a fost obinut o anumit valoare de atribut) ? prin afiarea regulilor care au dus la deducerea acelei valori, evideniind i valorile introduse de utilizator.

145

Bibliografie
[Bra88] [CM84] [Flo93] [LS93] [SS86] I. Bratko. Prolog Programming for Artificial Intelligence, Addison-Wesley, 1988. W. F. Clocksin i C. S. Mellish. Programming in Prolog, 2nd edition, Springer Verlag, 1984. A. M. Florea. Elemente de inteligen artificial, UPB, 1993. G. F. Lugger i W. A. Stubblefield. Artificial Intelligence Structures and Stategies for Complex Problem Solving, Benjamin/Cummings, 1993. L. Sterling i E. Shapiro. The Art of Prolog, The MIT Press, 1986.

146

147