Laboratorul 1
(Deadline: 10.03.2019 23:59:59)
Daca nu sunteti logati exercitiile nu se mai afiseaza.
Ce sunt sistemele expert?
Conform DEX 2009:
Sistem expert = program de exploatare inteligenta a unei baze de date caracteristice unui domeniu particular de aplicatie.
Cateva exemple de sisteme expert:
http://www.easydiagnosis.com/http://www.exsys.com/Demos/Dogs/DogSelectorDemoPage.html
http://en.akinator.com/personnages/
http://www.20q.net/
De ce prolog?
- simuleaza bine ideea de rationament, bazandu-se pe fapte si reguli folosite pentru a raspunde la diversele interogari
- in cautarea solutiilor foloseste un motor de backtracking incorporat. Programarea in alte limbaje, gen C sau Java ar impune crearea motorului in acel limbaj si de asemenea creare unui mediu in care pot fi definite faptele sau regulile necesare problemei de rezolvat.
- este printre cele mai folosite limbaje pentru rezolvarea problemelor de inteligenta artificiala.
- mai multe baze de cunostinte folosite in diverse aplicatii sunt scrise in Prolog. De exemplu: WordNet
Bune de citit:
- http://www.drdobbs.com/parallel/the-practical-application-of-prolog/184405220
- http://www.amzi.com/articles/youbet.htm
Introducere in Prolog
Limbaje de programare folosite in domeniul inteligentei artificiale: LISP, Prolog, STRIPS etc.Prolog - Programare logica, bazata pe predicate.
Notiuni de baza:
-
Constante - Pot fi numere (ex: 23, -5, 4.2, -1.0e-10 etc.) sau atomi. Atomii sunt secvente de caractere (alfanumerice sau,
conform
specificatiilor, din setul: + - * / \ ^ < > = ~ : . ? @ # $ &) care obligatoriu nu
pot incepe cu litera mare ori cu caracterul _ si nu pot contine spatii.
Atentie, caracterele alfanumerice nu trebuie amestecate cu celelalte (din setul de mai sus), adica, de exemplu, +++ este atom dar +m+ nu este
Atomii se pot scrie si intre apostroafe, caz in care in componenta lor pot
aparea si spatii iar sirul de caractere care se afla inauntrul apostroafelor poate in acest caz sa inceapa cu litera mare ori _.
De asemenea intre apostroafe putem avea si combinatii de caractere alfanumerice cu nealfanumerice, de exemplu, '+m+' este un atom.
Alte exemple de atomi:
abc, '_abc', a321, 'Alba ca Zapada'.
-
Variabile - Numele lor se reprezinta prin secvente de caractere care incep cu litera mare sau _
-
Fapte - Reprezinta afirmatii, cunostinte considerate adevarate.
predicat(arg1,...,argn).
-
Reguli
concluzie(...):- set de premise aflate in diverse relatii logice
-
Scopuri (interogari) - se folosesc pentru a obtine diverse informatii din
baza de cunostinte. Fie doar testeaza valoarea de adevar (daca interogarea nu contine variabile),
fie ofera setul de valori pentru variabilele din interogare astfel incat predicatul interogarii sa aiba valoarea de adevar true.
| ?- predicat_interogare(termen).
| ?- predicat_interogare(Variabila).
Obtinerea solutiilor pentru interogarile cu variabile se face in Prolog cu ajutorul unui mecanism de Backtracking intern.
Exista si interogari compuse. Acestea presupun o expresie logica formata din mai multe teste (interogari de predicate) care sunt legate cu diversii operatori logici. De exemplu, avem baza de cunostinte incarcata deja in consola Prolog:
num(2).
Sa presupunem ca vrem sa aflam toate sumele care se pot face din numere pare "de tip num" unde primul numar e mai mic decat al doilea. O astfel de interogare ar arata asa: | ?- num(X),X mod 2=:=0, num(Y), Y mod 2=:=0, X<Y, Sum is X+Y.
num(8).
num(3).
num(10).
num(7).
X = 2,
Y = 8,
Sum = 10 ? ;
X = 2,
Y = 10,
Sum = 12 ? ;
X = 8,
Y = 10,
Sum = 18 ? ;
no
| ?-In cazul in care vrem sa afisam doar sumele, nu si valorile lui X si Y, scriem varibilele precedate de un _ (interpretorul Prolog nu afiseaza valorile variabilelor din interogare ale caror nume incep cu _ ).
| ?- num(_X),_X mod 2=:=0, num(_Y), _Y mod 2=:=0, _X<_Y, Sum is _X+_Y.
Sum = 10 ? ;
Sum = 12 ? ;
Sum = 18 ? ;
no
| ?-
Crearea unui program (sau mai corect spus, baza de cunostinte) in prolog
Programul se va scrie intr-un fisier text care se va salva cu extensia pl. Se poate folosi orice editor de texte, inclusiv notepad insa va recomand Notepad++ sau SWI-Prolog-Editor deoarece acesta afiseaza in culori diferite diversele elemente de sintaxa facand mai usoara atat citirea programului cat si depistarea erorilor.
Pana acum programele scrise de voi erau practic un set ordonat de instructiuni prin care ii spuneati calculatorului ce sa faca si mai ales cum sa realizeze ce avea de facut, ii dadeati indicatii precise asupra actiunilor pe care trebuia sa le indeplineasca. Insa in Prolog nu mai avem instructiuni ci predicate, ii indicam calculatorului ce sa faca (anume care e scopul la care sa ajunga, pe care sa-l indeplineasca) dar nu-i mai indicam efectiv si cum sa faca asta. Fisierele pe care le scriem in prolog sunt de fapt baze de cunostinte. O baza de cunostinte e un set de fapte si reguli care vor ajuta la realizarea rationamentelor.
Pentru a putea folosi baza de cunostinte, aceasta va trebui mai intai incarcata in memorie; aceasta se va face cu ajutorul instructiunii consult
| ?-consult('cale_fisier').
Calea poate fi absoluta sau relativa la active directory.
Pentru a vedea toate faptele si regulile incarcate in memorie, se foloseste predicatul fara parametri:
listing.
Daca dorim sa afisam doar faptele si regulile corespunzatoare unui anume predicat folosim:
listing(nume_predicat).
Comentarii
% | pana la sfarsitul liniei |
/* ... */ | intre cele doua semne. |
Operatori
Comparatii intre termeni generali
Acesti operatori pot fi folositi pe orice tip de termeni (atomi, variabile, numere, expresii compuse). Atentie insa, ei nu efecteaza calcule, de exemplu 2+3==3+2 este fals, deoarece compara expresiile, nu valorile expresiilor.
@< | mai mic |
@=< | mai mic sau egal |
@> | mai mare |
@>= | mai mare sau egal |
== | egalitate intre termeni |
\== | termeni diferiti |
Observatie:Daca avem un atom a carui forma ne permite sa il folosim fara apostroafe (de exemplu: abc), forma lui cu apostroafe, (adica, pentru exemplul nostru: 'abc'), reprezinta aceeasi valoare.
| ?- abc=='abc'.yes
Atentie: nu se intampla acelasi lucru si pentru numere. Daca scriem 1 fara apostroafe, il vede ca numar, pe cand forma '1' reprezinta un atom, deci valorile sunt diferite.
| ?- 1=='1'.no
| ?-
Comparatii aritmetice
< | mai mic |
=< | mai mic sau egal |
> | mai mare |
>= | mai mare sau egal |
=:= | egalitate aritmetica |
=\= | inegalitate aritmetica |
Observatie importanta: in cadrul comparatiilor aritmetice ambii operanzi trebuie sa contina numai variabile instantiate.
Operatori logici
, | si |
; | sau |
\+ | negare |
Sa luam ca exemplu un predicat care verifica daca un numar X se afla in intervalul inchis [A, B].
Predicatul va trebui sa primeasca drept parametri de intrare toate cele 3 numerele. In plus predicatul va trebui sa verifice doua inegalitati: A =< X si X =< B.
In exemplul de mai jos am scris si specificatia predicatului. Aceasta apare ca un comentariu deasupra regulii (%in_interval(+A, +B, +X)
). In dreptul fiecarui parametru s-a pus simbolul + pentru a se preciza faptul ca parametrii sunt toti de intrare (adica trebuie apelat predicatul doar cu argumente instantiate). Specificatia este doar o conventie (este un comentariu, dupa cum observati). Si doar ne ajuta sa intelegm cum trebuie folosit predicatul - fiind doar comentariu, nu are rol efectiv in program.
%in_interval(+A, +B, +X)
in_interval(A, B, X):- A=<X, X=<B.
Putem vedea ca functioneaza mai jos:
| ?- in_interval(3,7,5).yes
| ?- in_interval(3,7,10).
no
Dorim acum sa facem un predicat care verifica faptul ca un numar X se afla in afara unui interval inchis A, B.
Cu alte cuvinte, pe axa reala se afla fie in stanga lui A (X < A), fie in dreapta lui B (X > B).
%not_interval(+A, +B, +X)
not_interval(A, B, X):- X < A ; X > B.
Am putea verifica faptul ca numarul X nu se afla in interval chiar in mod direct, cu ajutorul predicatului in_interval definit mai devreme
%in_interval(+A, +B, +X)
in_interval(A, B, X):- A=<X, X=<B.
%not_interval_2(+A, +B, +X)
not_interval_2(A, B, X):- \+ in_interval(A, B, X).
Specificatii predicate
Atunci cand vrem sa folosim un predicat trebuie sa stim clar care sunt parametrii sai de intrare (ce argumente trebuie date) si care sunt parametrii de iesire (cei informatii stie sa ofere/calculeze).
De exemplu avem un predicat care calculeaza dublul unui numar.
dublu(N, D):-number(N), D is 2*N.
Din modul de declarare al regulii ne dam seama ca N este numarul dat si D este dublul lui (calculat).
Totusi pentru un predicat cufoarte multe clauze si multi parametri ar fi complicat sa-i citim definitia de fiecare data cand avem nevoie sa il folosim. In plus poate dorim sa folosim predicate dintr-un modul de biblioteca, la al carui cod efectiv nu avem acces.
Cum se clarifica parametrii predicatului? Prin niste prefize specifice.
Prefixe pentru parametrii
+ | Parametru de intrare (argumentul transmis predicatului trebuie sa aiba valoare cunoscuta). |
- | Parametru de iesire (argumentul nu trebuie sa fie instantiat; va fi o variabila a carei valoare este calculata). |
? | Parametru care poate fi si de intrare si de iesire (argumentul dat poate sa aiba valoare cunoscuta sau sa fie o variabila neinstaniata). |
Deci, pentru predicatul dublu
definit mai sus specificatia ar arata astfel:
%dublu(+Numar,-Dublu)
dublu(N, D):-number(N), D is 2*N.
Astfel, acum vazand specificatia stim ca modul in care s-a dorit sa se apeleze (interogheze) predicatul este:
| ?- dublu(2,N).
N = 4 ?
yes
Observam totusi ca putem apela predicatul si asa:
| ?- dublu(2,4).
yes
| ?- dublu(2,3).
no
| ?-
Deci ar functiona si cu parametrul a doilea cunoscut. Asta inseamna ca specificatia
%dublu(+Numar,-Dublu)
e gresita si ca trebuia de fapt %dublu(+Numar,?Dublu)
? Adica sa aratam clar ca al doilea paramteru poate fi si dat si calculat? Nu neaparat. Specificatia e subiectiva si arata cum a intetionat autorul sa fie folosit predicatul. Nu trebuie sa acopere toate cazurile posibile de transmitere a argumentelor.
Atentie, specificatiile se pun doar in comentarii. Nu reprezinta o parte din sintaxa prolog ci sunt doar o conventie de a arata concis care sunt parametrii de intrare si iesire. O gresala frecventa pe care cei incepatori in prolog o fac e sa puna specificatiile direct in cod cand definesc regula:
dublu(+N, -D):-number(N), D is 2*N.%gresit!
/*mai sus nu au fost scrise specificatiile predicatului
ci s-a precizat mai degraba ca predicatul dublu primeste ca prim paramteru
un termen compus cu operatorul unar +,
si al doilea parametru un termen compus cu operatorul - */
Prioritati
Atentie! Operatorul si (,) este prioritar sau-ului (;). De aceea daca avem doua sau mai multe cazuri inainte de care sau dupa care vrem sa aplicam aceeasi operatie, trebuie sa punem testarea cazurilor in paranteza:
....., (...;...;...), .... .
De exemplu, dorim sa facem un predicat afis_paritate(+Nr) care afiseaza un mesaj de genul "Nr este par/impar" sau esueaza daca argumentul dat nu e numar. Un exemplu de interogarea ar fi acesta:
| ?- afis_paritate(2).2 este par
yes
| ?- afis_paritate(3).
3 este impar
yes
| ?- afis_paritate(a).
no
| ?-
Ne gandim sa scriem predicatul in felul urmator. Testam intai ca argumentul dat e numar intreg. Apoi daca e par setam o variabila numita Parit sa fie egala cu atomul par, iar daca e impar, setam Parit la valoarea impar. Apoi afisam rezultatul folosindu-ne de Parit.O prima incercare ar putea arata ca mai jos.
%predicatul gresit
afis_paritate(X):-integer(X), X mod 2=:=0, Parit=par; X mod 2 =\=0, Parit=impar, write(X), write(' este '), write(Parit).
Totusi observam ca nu ne da rezultatul dorit:
| ?- afis_paritate(3).3 este impar
yes
| ?- afis_paritate(2).
yes
| ?- afis_paritate(a).
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal: a mod 2=\=0
| ?-
Numai pentru numere impare functioneaza asa cum ne-am fi asteptat. De ce? Pentru ca si-ul este prioritar sau-ului. Deci practic el grupeaza testele din afis_paritate in doua cazuri legate prin si:
integer(X), X mod 2=:=0, Parit=par
X mod 2 =\=0, Parit=impar, write(X), write(' este '), write(Parit)
Deci observam ca pe cazul 1 nu mai face afisarea, iar pe cazul 2 nu mai testeaza ca e numar intreg ca sa poata fi sigur ca mod e o operatie valida, asa cum se vede si in trace-ul de mai jos, unde, dupa ce esueaza integer, in loc sa iasa din predicat cu no, asa cum am fi dorit, continua pe cazul 2:
| ?- trace.% The debugger will first creep -- showing everything (trace)
yes
% trace
| ?- afis_paritate(a).
1 1 Call: afis_paritate(a) ?
2 2 Call: integer(a) ?
2 2 Fail: integer(a) ?
3 2 Call: a mod 2=\=0 ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal: a mod 2=\=0
3 2 Exception: a mod 2=\=0 ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal: a mod 2=\=0
1 1 Exception: afis_paritate(a) ?
! Domain error in argument 1 of =\= /2
! expected expression, but found a
! goal: a mod 2=\=0
% trace
| ?-
Tot ce trebuie sa facem e sa grupam intr-o paranteza cazurile astfel incat sa se aplice penru ambele cazuri si testele de dinaintea lor si testele de dupa
afis_paritate_corect(X):-integer(X), (X mod 2=:=0, Parit=par; X mod 2 =\=0, Parit=impar), write(X), write(' este '), write(Parit).
E practic ideea de la adunare si inmultire: 2*(3+5)*7=2*3*7+2*5*7. Aici prin desfacerea parantezei s-ar ajunge la cazurile:
- integer(X), X mod 2=:=0, Parit=par, write(X), write(' este '), write(Parit)
- integer(X), X mod 2=\=0, Parit=impar, write(X), write(' este '), write(Parit)
Astfel, pe ambele cazuri se testeaza daca X e intreg (si deci e valida operatia mod), si pe ambele cazuri se afiseaza mesajul.
Diverse predicate de testare a tipului
atom(+Termen)
- indica daca termenul este un atomatomic(+Termen)
- indica daca termenul este un numar sau un atomnumber(+Termen)
- indica daca termenul respectiv este numarfloat(+Termen)
- indica daca termenul respectiv este numar cu zecimaleinteger(+Termen)
- indica daca termenul respectiv este numar intregvar(+Termen)
- indica daca argumentul primit este neinstantiatnonvar(+Termen)
- opusul lui var, (nonvar(X) e echivalentul lui \+var(X) )
Unificare. Operatorul de unificare. Functii matematice.
Operatorul is
se foloseste pentru a calcula expresii numerice si pentru a aloca valoarea respectiva unei variabile.
O lista cu operatorii si functiile matematice suportate de Sicstus Prolog gasiti in
documentatie.
Unificarea se realizeaza cu ajutorul operatorului =.
Pentru termeni simpli exista mai multe cazuri:
- atat termenul din dreapta cat si termenul din stanga sunt constante, caz in care = se comporta ca un == (testand efectiv egalitatea termenilor).
- termenul din dreapta e e o constanta si termenul din stanga e o variabila neinstantiata, sau invers, termenul din stanga e constanta si cel din dreapta e variabila neinstantiata; in acest caz practic temenul neinstantiat primeste valoarea constantei (se realizeaza o unificare intre variabila si acea constanta)
- atat termenul din dreapta cat si cel din stanga sunt variabile neinstantiate. In acest moment prin unificare practic ambele variabile se refera la acelasi obiect variabil (va puteti gandi ca este aceeasi variabila cu doua nume).
Variabila anonima
Se noteaza cu _ si reprezinta o variabila a carei valoare nu ne intereseaza, in principiu putem avea acolo "orice". Se foloseste pentru variabile care altfel ar aparea drept singleton, acestea sunt variabile care apar doar o singura data in cadrul unei reguli, prin urmare nu exista restrictii asupra lor. Desi mesajul de singleton variables e doar un warning e bine sa il evitati in program pentru a avea un cod mai usor de urmarit.
Structuri(termeni compusi)
O structura se aseamana unui predicat din punct de vedere sinctactic, in sensul ca are aceeasi forma: nume_structura(elem1,elem2,...). Insa, spre deosebire de predicate, o structura nu e vazuta ca avand valoare de adevar, e doar un termen care grupeaza mai multe date. Pentru a testa daca un termen este compus sau nu, se folosesc predicatele:
-
simple(+Termen) - va fi adevarat daca Termen nu este compus
Exemple: | ?- simple(3).
yes
| ?- simple(a(b,c)).
no
| ?- simple(T).
true ?
yes
-
compound(+Termen) - opusul lui
Exemple: | ?- compound(T).simple
, va fi adevarat daca Termen este compus.
no
| ?- compound(a(b,c)).
yes
Comparatii intre termeni compusi
Se realizeaza comparatiile obisnuite pe termeni.
yes
| ?- om(ion, popescu)==om(ion, popescu).
yes
| ?- om(ion, popescu)==pisica('Mitzi', Y).
no
Unificarea in cazul termenilor compusi.
Atunci cand unul din termeni e neinstantiat evident va primi ca valoare acea structura.
X = om(ion,popescu) ?
yes
Daca ambii termeni ai unificarii sunt instantiati cu o anumita structura,
pentru a se realiza unificarea,
trebuie in primul rand ca numele structurilor si numarul de parametri sa coincida,
apoi practic se va realiza
unificarea pentru fiecare componenta a structurii.
Mai jos in primul caz raspunsul e no
pentru ca numele termenilor compusi nu coincid,
iar in al doilea caz numarul de parametri nu coincide:
no
| ?- ab(X,Y)=ab(X,Y,Z).
no
Daca structurile sunt compatibile, ca in exemplul de mai jos, se realizeaza unificarea pe fiecare componenta a structurii:
X = 'Mitzi',
Y = persana ?
| ?- pisica(X,persana,Y)=pisica('Mitzi',persana,varsta(2,ani)).
X = 'Mitzi',
Y = varsta(2,ani) ?
Mai jos structurile nu sunt compatibile deoarece difera a doua componenta:
no
Predicatele true si fail
Predicatele true
si fail
nu au argumente si au proprietatea ca true este intotdeauna adevarat
si fail este intotdeauna fals.
| ?- true.
yes
| ?- fail.
no
In Sicstus Prolog exista si echivalentele: otherwise
si false
, insa e mai bine sa folositi true si fail deoarece acestea sunt definite
si in standardul ISO.
Observatie. Evident, daca la inceputul unei expresii logice in care testele sunt legate prin "si" avem un fail, nu se va realiza nici un test de dupa fail, si cazul respectiv se va termina cu esec
afisare(X):-fail, write(X),nl.
no
Putem sa ne folosim de fail
pentru a itera prin toate faptele din baza de cunostinte.
De exemplu, ca sa afisam cifrele de la 0 la 9 pe un rand.
cifra(0). cifra(1). cifra(2). cifra(3). cifra(4). cifra(5). cifra(6). cifra(7). cifra(8). cifra(9).
%primul "caz" din cele doua clauze ale lui afis_cifre
%datorita lui fail se tot intoarce (prin backtracking) la cifra(X)
%si afiseaza urmatoarea cifra din baza de cunostinte
afis_cifre :- cifra(X), write(X), fail.
afis_cifre.
Interogam:
| ?- afis_cifre.
0123456789
yes
Predicatul afis_cifre se mai putea scrie si cu o singura clauza, folosind predicatul true:
afis_cifre :- cifra(X), write(X), fail; true.
Cut
Cut se noteaza cu ! si reprezinta operatia de oprire a backtracking-ului.
Despre el puteti citi mai multe in documentatie
dar si in
capitolul scris de Blackburn, Bos si Striegnitz
Exista doua tipuri de cut:
- Cut verde - folosit doar pentru optimizare, eliminarea lui nu strica logica programului,
clauzele predicatului pot fi rescrise si in alta ordine.
max(X,Y,X):- X>=Y, !.
max(X,Y,Y):- X<Y. - Cut rosu - practic cand nu este vorba de un cut verde, eliminarea lui sau
schimbarea ordinii clauzelor va schimba logica programului.
max(X,Y,X):- X>=Y, !.
max(X,Y,Y).
Predicatul once
Are aceeasi sintaxa ca si call, de exemplu, once(p(X,Y)), insa realizeaza o singura interogare pentru predicatul p(X,Y).
Sa urmarim exemplul:
culoare(rosu).
culoare(verde).
culoare(albastru).
afis_prima_culoare:- once(culoare(X)), write(X), nl, fail; true.
afis_prima_culoare_b:- once((culoare(X),write(X), nl)), fail; true.
afis_prima_culoare_gresit:- once((culoare(X),write(X), nl, fail)); true.
Intai incercam interogarea:
| ?- once(culoare(X)).
X = rosu ? ;
no
Observam ca desi am tastat ; pentru o noua solutie, nu am primit urmatoarea culoare, deoarece once face ca, la revenirea prin bactracking, reinterogarea se termine cu esec.
Ceva similar observam si la interogarile predicatelor:
| ?- afis_prima_culoare.
rosu
yes
| ?- afis_prima_culoare_b.
rosu
yes
Din cauza predicatului fail, executia se intoarce la once, iar acesta esueaza, impingand executia pe ramura cu true.
Totusi in interiorul expresiei primite de predicatul once se pot efectua intoarceri si obtine noi solutii, asa cum se vede din apelul predicatului afis_prima_culoare_gresit:
| ?- afis_prima_culoare_gresit.
rosu
verde
albastru
yes
Expresii conditionale in prolog
Desi se poate simula cu ajutorul operatorilor logici ; , \+
uneori e mai usor sa folosim
constructiile built-in speciale.
Simularea if..else.. cu ajutorul a ceea ce s-a invatat pana acum se facea sub forma:
Atentie, desi in unele cazuri merge sa nu mai punem si \+ conditie(...), acest lucru in general nu e corect.
Un exemplu in care putem sa eliminam negarea conditiei ar fi urmatorul predicat cu un parametru care afiseaza par daca parametrul e par si impar in caz contrar:
par
yes
| ?- p(3).
impar
yes
| ?-
X mod 2=:=0
si atunci va ajunge pe cealalta ramura a lui sau afisand "impar".
Insa daca ne uitam la urmatorul exemplu:
p1(X,Y):-X mod 2 =:= 0, Y=par; Y=impar.
yes
| ?- p1(2,impar).
yes
| ?- p1(2,X).
X = par ? ;
X = impar ? ;
no
| ?-
Varianta corecta e testarea negatiei conditiei in a doua ramura a lui sau:
In acest caz totusi mai exista o varianta de rezolvare a problemei folosind ! (cut), dar care e in acest caz un cut rosu:
pred(ExprVar):-conditie(ExprVar1), !, prelucrari_conditie_adevarata(ExprVar2); prelucrari_conditie_falsa(ExprVar3).
unde ExpVar-urile pot fi o variabila sau un set de variabile.
In exemplul de mai sus p3 functioneaza pentru ca daca X e numar par, il instantiaza pe Y la valoarea par, si daca cerem o noua solutie pentru Y, cand se va intoarce din backtracking la cut, se va opri, afisand no fiindca nu mai gaseste (sau mai bine zis, nu mai cauta) alte solutii. Daca X e impar in urma testului X mod 2 =:=0 va trece automat pe ramura a doua a lui sau.
Sa vedem inca un exemplu similar. Daca conditia e indeplinita, se interogheaza q(X,Y), daca nu, se interogheaza z(X,Y).
z(2,b).
z(3,z).
q(3,c).
q(3,cc).
q(4,d).
conditie(X):-X>2.
%pred(+X,-Y)
pred(X,Y):-conditie(X),!,q(X,Y);z(X,Y).
Y = c ? ;
Y = cc ? ;
no
| ?-
Totusi cut se poate folosi doar cand in cadrul expresiei conditiei nu se calculeaza valori pentru eventuale variabile
ce se vor folosi mai tarziu in regula, sau ne intereseaza doar prima solutie pentru aceste variabile. De
asemenea alt dezavantaj este faptul ca daca folosim cut nu mai putem trece la clauzele urmatoare ale predicatului definit.
Sa luam ca exemplu:
proprietate_nr(Nr,P):- integer(Nr),!, P='rational intreg'; P='rational neintreg'.
proprietate_nr(_,real).
P = 'rational intreg' ? ;
no
| ?- proprietate_nr(3.5,P).
P = 'rational neintreg' ? ;
P = real ? ;
no
| ?-
animal(hamster).
detine(ion, caiet).
detine(ion, pisica).
detine(ion, hamster).
detine(ion, carte).
detine(george,calculator).
%ce_animale_are(+Nume,-Animal)
ce_animale_are(Nume,A):-detine(Nume,A1),animal(A1),!,A=A1;A=niciunul.
A = niciunul ? ;
no
| ?- ce_animale_are(ion,A).
A = pisica ? ;
no
Observatie: clauza de mai sus se putea scrie intr-adevar mai simplu:
Prin urmare cut-ul in anumite situatii, creeaza probleme si in acele cazuri trebuie sa folosim negarea conditiei.
Predicatul if
Predicatul if accepta trei parametri, fiecare din cei trei parametri reprezentand un termen compus, care reprezinta o expresie logica valida formata din predicate si operatori logici. Primul parametru reprezinta conditia. In cazul in care evaluarea conditiei se termina cu succes, se evalueaza expresia din parametrul al doilea; daca, in schimb, conditia are ca rezultat no (esec) este evaluat parametrul al treilea.
Operatorul "daca...atunci..." ()->();()
Daca expresia dinainte de -> este adevarata atunci este evaluata expresia din mijloc, altfel e evaluata ultima expresie. Este important sa puneti parantezele care grupeaza expresiile de evaluat, din cauza prioritatii operatorilor. Este de asemenea important sa puneti o paranteza in jurul intregii expresii, din acelasi motiv: (()->();()). In cazul expresiilor mai complexe este o practica buna sa indentati corespunzator programul pentru a nu se genera confuzii (mai ales ca ; poate fi usor confundat cu un "sau" la o parcurgere in graba a codului).
Atentie expresia de dinainte de -> se evalueaza doar o data, adica daca se revine din backtracking la o sintagma de forma ()->();() nu se va reevalua pentru instantierea variabilelor calculate cu alte valori.
Partea de else a expresiei poate sa lipseasca, adica sa fie doar ()->(), in cazul acesta expresia e echivalenta cu ()->();fail
Exemplu:
p(2).
p(3).
p(4).
p(5).
pred_if1:-if(
(p(X),X>3),
(write(X), write(' e mai mare ca 3\n')),
(write('conditie falsa\n'))
),fail;true.
pred_if1a:-if(
(p(10)),
(write(X), write(' e mai mare ca 3\n')),
(write('conditie falsa\n'))
),fail;true.
pred_if2:-((p(X),X>3)->
(write(X), write(' e mai mare ca 3\n'));
write('conditie falsa\n')
),fail;true.
4 e mai mare ca 3
5 e mai mare ca 3
yes
| ?- pred_if1a.
conditie falsa
yes
| ?- pred_if2.
4 e mai mare ca 3
yes
| ?-
Observati pe exemplul de mai sus ca pred_if1 a afisat mesaje doar pentru acei X mai mari decat 3. De ce? Cum poate fi modificat predicatul pred_if1 din programul de mai sus astfel incat pentru fiecare solutie X a lui p(X) sa afiseze un mesaj daca X e mai mare ca 3 si alt mesaj daca nu.
Recursivitate
Recursivitatea apare atunci cand valoarea de adevar a unui predicat cu anumiti parametri
depinde de valoarea de adevar a aceluiasi predicat (dar, bineinteles, cu alti parametri). Practic e vorba de un
predicat care se apeleaza pe el insusi (direct sau indirect), de exemplu:
p(...) :- ..., p(...), ... .
sau:
p1(...) :- ..., p2(...), ... .
p2(...) :- ..., p1(...), ... .
Exemple
Afisarea a N numere
Dorim sa scriem un predicat care afiseaza de N ori un caracter. In prolog nu avem for, deci trebuie sa il simulam cumva.
Cum trebuie sa executam in mod repetitiv aceeasi actiune de N ori putem apela recursiv un predicat care face acea actiune, trimitandu-i ca parametru si de cate ori ar trebui sa mai repete actiunea. De fiecare data cand este efectuata actiunea insa trebuie sa ii transmitem ca are de facut cu una mai putin astfel de actiuni. Deci ii vom transmite N-1 in apelul recursiv.
%afis_chr(+Chr,+Nr)
%afiseaza de Nr ori caracterul Chr
/*
daca numarul e 0,
predicatul nu ar trebui sa mai afiseze nimic
motiv pentru care nici nu ne mai intereseaza valoarea lui Chr
*/
afis_chr(_,0).
/*
Pentru un numar Nr>0,
afisam un caracter Chr.
Scadem din Nr numarul 1 (obtinand Nr1)
pentru caracterul afisat.
Apelam recursiv afis_chr cu Nr1.
*/
afis_chr(Chr,Nr):- Nr>0, write(Chr), Nr1 is Nr-1, afis_chr(Chr,Nr1).
Programul va afisa:
| ?- afis_chr('#',10).
##########
yes
Observam randul:
afis_chr(_,0).
Acesta este pasul de oprire (deoarece nu mai apeleaza inca o data afis_chr) si se executa cand N a ajuns la 0 (dar, evident, se executa si in cazul partcular cand N a fost de la inceput 0).
daca stergem pasul de oprire o sa vedem ca afisarea se efectueaza in continuare:
| ?- afis_chr('#',10).
##########
no
dar, la final, predicatul esueaza (pentru ca nu mai are niciun caz definit pentru N <e; 0).
Predicat care face suma unei serii
Dorim sa facem suma 1+1/2+1/3+....+1/N unde N e un numar natural mai mare strict decat 0, transmis ca argument.
Cum in enunt se precizeaza ca N>0 nu trebuie sa definim cazuri pentru el negativ (si daca se da un numar negativ, predicatul va esua - asa cum si este comportamentul asteptat).
Din nou observam o operatie repetitiva si anume adunarea cu un 1/K unde 1<e;K<e;N. Astfel avem doua variante (la fel de corecte):
- mergem cu K de la 1 la N
- mergem cu K de la N la 1
- ori (...(1)+ 1/2) + 1/3) +....) + 1/(N-1)) + 1/N
- 1+ (1/2 + (....+(1/(N-1) + (1/N))...)
%suma_fractii_a_1(+N,-S)
suma_fractii_a1(N,S):-sum_aux_a1(1,N,0,S).
%sum_f1_aux(+K, +N, +S_partial, -S_final)
sum_aux_a1(K, N, S_partial, S_final):-
K =< N,
S_partial_nou is S_partial + 1/K,
K1 is K+1, sum_aux_a1(K1, N, S_partial_nou, S_final).
sum_aux_a1(K, N, S_partial, S_final):- K=:=N+1, S_final = S_partial.
% randul de mai sus se putea scrie mai scurt:
% sum_f1_aux(K, N, S_partial, S_partial):- K=:=N+1.
suma_fractii_a2(N, S) :- sum_aux_a2(1, N, S).
sum_aux_a2(K, N, S_partial_nou) :-
K =< N, K1 is K + 1,
sum_aux_a2(K1, N, S_partial),
S_partial_nou is S_partial + 1/K.
% de ce nu merge sum_aux_a2(N, N+1, 0).
sum_aux_a2(K, N, 0):- K=:=N+1.
suma_fractii_b1(N, S):- sum_aux_b1(N, 0, S).
sum_aux_b1(N, S_partial, S_final) :-
N > 1,
S_partial_nou is S_partial + 1/N,
N1 is N - 1, sum_aux_b1(N1, S_partial_nou, S_final).
sum_aux_b1(1, S_partial, S_final):- S_final is S_partial+1.
suma_fractii_b2(N, S_partial_nou) :-
N > 1, N1 is N - 1,
suma_fractii_b2(N1, S_partial),
S_partial_nou is S_partial + 1/N.
suma_fractii_b2(1, 1).
| ?- suma_fractii_a1(3,S). S = 1.8333333333333333 ?
yes
| ?- suma_fractii_a2(3,S).
S = 1.8333333333333333 ?
yes
| ?- suma_fractii_b1(3,S).
S = 1.8333333333333333 ?
yes
| ?- suma_fractii_b2(3,S).
S = 1.8333333333333333 ?
yes
O suma cu semne alternate
Suma alternata 1-1/2+1/3-1/4+1/5-1/6+....+(-1)(N+1)/N
suma_alternata_a(N, S) :- aduna_nr(1, N, 0, S).
aduna_nr(K, N, S_partial, S_final) :- K < N, S_partial_nou is S_partial + 1/K, K1 is K + 1, scade_nr(K1, N, S_partial_nou, S_final).
aduna_nr(N, N, S_partial, S_final) :- S_final is S_partial + 1/N.
scade_nr(K, N, S_partial, S_final) :- K < N, S_partial_nou is S_partial - 1/K, K1 is K + 1, aduna_nr(K1, N, S_partial_nou, S_final).
scade_nr(N, N, S_partial, S_final) :- S_final is S_partial - 1/N.
suma_alternata_b(N, S) :- sum_alt_aux(1, N, 0, S).
| ?- suma_alternata_a(4,S).
sum_alt_aux(K,N,S_partial,S_final):- K =< N, (K mod 2=:=0 -> S_partial_nou is S_partial - 1/K ; S_partial_nou is S_partial + 1/K), K1 is K+1, sum_alt_aux(K1,N,S_partial_nou,S_final).
sum_alt_aux(K,N,S_partial,S_final):- K=:=N+1, S_final=S_partial.
S = 0.5833333333333333 ?
yes
| ?- suma_alternata_b(4,S).
S = 0.5833333333333333 ?
yes