You are on page 1of 93

Universitatea de Stat din Moldova

Facultatea de Matematică şi Informatică

S. Lâsâi, G. Sturza, V. Grigorcea

INTRODUCERE
ÎN PROGRAMAREA LOGICĂ
CICLU DE PRELEGERI

&
- Chişinău 2003 -

0
Prefaţă

Lucrarea de faţă tratează câteva aspecte ale programării logice şi este o


încercare de a oferi studenţilor un material util în unul din domeniile actuale ale
informaticii.
Principalele obiective ale lucrării sunt:
- limbajul Prolog (versiunea Borland);
- programarea logică;
- domeniile de aplicaţie ale programării logice.
Domeniile de aplicaţie sunt vaste. Noi ne-am limitat doar la unele
probleme din teoria grafurilor, bazelor de date relaţionale şi la unele probleme
din Inteligenţa Artificială.
Cursul "Programarea logică" urmează cursului "Logica formală" şi
precede cursul "Inteligenţa Artificială".
Acest curs de prelegeri este destinat viitorilor licenţiaţi în Informatică.
În capitolul 1 se trec în revistă unele rezultate ale demonstrării automate
necesare înţelegerii programării logice.
În capitolele 2 şi 3 sunt descrise elementele de bază ale limbajului Prolog
şi structura unui program.
Capitolul 4 prezintă metodele de procesare a programelor Prolog.
În capitolul 5 se examinează listele, obiectele compuse şi lucrul cu aceste
obiecte.
Lucrul cu ferestrele în limbajul Prolog este examinat în capitolul 6.
Cele mai uzuale predicate standard (predefinite, built-in) sunt
exemplificate în capitolul 7.
Capitolele 8 şi 9 includ lucrul cu baze de date dinamice (interne) şi baze
de date relaţionale. Se examinează modurile de reprezentare a relaţiilor în Prolog
şi operaţiile algebrei relaţionale.
Metodele programării nondeterministe sunt descrise în capitolul 10.
Domeniul larg de aplicaţii ale programării logice este examinat în
capitolul 11, unde sunt oferite un şir de probleme rezolvate.
Capitolul 12 este consacrat bazelor de date externe. Se trec în revistă toate
predicatele de lucru cu baze de date externe, fiind însoţite de un şir de exemple.
În lucrarea de faţă nu se examinează "Grafica în Prolog".

1
Sumar
1. Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.1. Principiul rezoluţiei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2. Unificare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3. Prolog şi programarea logicii predicatelor . . . . . . . . . . . 7
1.4. Ce este un program Prolog? . . . . . . . . . . . . . . . . . . . . . . 8
2. Elemente constitutive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1. Tipuri de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2. Variabile. Constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3. Clauze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.1. Fapte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3.2. Reguli . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.3.3. Interogări (goal) . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4. Predicate aritmetice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.5. Intrări. Ieşiri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3. Structura programelor Turbo Prolog . . . . . . . . . . . . . . . . . . . . . 19
4. Procesarea programelor Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1. Căutarea soluţiilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2. Scopuri deterministe şi nondeterministe. Predicatele fail,
cut şi not . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3. Recursivitate. Simulări de cicluri . . . . . . . . . . . . . . . . . . 28
4.4. Predicatul repeat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5. Prelucrări de obiecte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.1. Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2. Lucrul cu listele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.3. Predicatul findall . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.4. Sortarea listelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.5. Obiecte compuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6. Ferestre în Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7. Predicate predefinite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

2
7.1. Predicate de lucru cu caractere şi string-uri . . . . . . . . . . 46
7.2. Predicate de lucru cu fişiere . . . . . . . . . . . . . . . . . . . . . . 49
7.3. Predicatele bound şi free . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.4. Predicatul include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.5. Predicatul exit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.6. Predicatul code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
7.7. Predicatele date şi time . . . . . . . . . . . . . . . . . . . . . . . . . . 52
7.8. Predicatele beep şi sound . . . . . . . . . . . . . . . . . . . . . . . . 52
8. Baze de date interne (dinamice) . . . . . . . . . . . . . . . . . . . . . . . . 53
9. Prolog şi baze de date relaţionale . . . . . . . . . . . . . . . . . . . . . . . 58
9.1. Reprezentarea relaţiilor . . . . . . . . . . . . . . . . . . . . . . . . . . 58
9.2. Algebra relaţională . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
9.3. Interogarea bazelor de date . . . . . . . . . . . . . . . . . . . . . . . 60
10. Programarea nondeterministă . . . . . . . . . . . . . . . . . . . . . . . . . . 64
10.1. Metoda "generează-testează" . . . . . . . . . . . . . . . . . . . . 64
10.2. Problema colorării unei hărţi . . . . . . . . . . . . . . . . . . . . 67
11. Aplicaţii Prolog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
11.1. Legi de compoziţie . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
11.2. Grafuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
11.3. Staţia de telefoane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
11.4. Problema "Maimuţa şi banana" . . . . . . . . . . . . . . . . . . . 74
12. Baze de date externe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
12.1. Crearea, deschiderea şi închiderea unei baze externe . . 79
12.2. Operaţii asupra lanţurilor . . . . . . . . . . . . . . . . . . . . . . . 81
12.3. Lucrul cu B+ arbori . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Bibliografie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

3
1. Introducere
Logica matematică (formală) îşi are începutul în cercetările lui Aristotel
(384-322 î.e.n.). Meritele deosebite ale lui Aristotel constau în faptul că el
primul a reuşit să sistematizeze şi să codifice procedurile de raţionament.
Leibniz G.W. (1646-1716) este considerat nu numai unul dintre
fondatorii analizei matematice, dar şi a informaticii şi a logicii formale. În logică
el s-a ocupat de metodele ce permit descompunerea tuturor noţiunilor umane în
astfel de noţiuni care ar constitui "alfabetul raţiunii umane", scopul fiind
îmbinarea lor în mod automat pentru a obţine toate afirmaţiile adevărate. La
baza proiectului său a fost pusă Logica simbolică sau cum o numea el –
"Calculul care generează raţionamente". Menţionăm că Prolog este unul dintre
primele limbaje care a produs trecerea de la prelucrarea numerică la cea
simbolică.
La sfârşitul secolului XIX - începutul secolului XX o pleiadă de
matematicieni de primă mărime (Boole George 1815-1864, de Morgan 1806-
1871), Frege 1848-1925, Hilbert David 1862-1943, Herbrant, Turing A. 1912-
1954 ş.a.) a obţinut rezultate remarcabile în domeniul logicii matematice. Astfel
au fost puse bazele teoretice ale calculatoarelor electronice. Rezultatele lui
Herbrant din teza de doctor (1931) au stat la baza principiului rezoluţiei.

1.1. Principiul rezoluţiei


Principiul rezoluţiei reprezintă o metodă de inferenţă şi poate fi definit
astfel:
- Din AB şi AC putem deduce BC.
- De la formula F(x1, x2, …, xn), care o considerăm adevărată pentru
toate valorile x1, x2, …, xn, se poate trece la F(f1, f2, …,fn) prin
substituţia {fi / xi} (i = 1, 2, …, n).
Exemplu. Admitem că formula conţine clauzele: 1) BA,
2) CB,
3) C.

4
Să demonstrăm A. Pentru aceasta adăugăm clauza A. Atunci:
1) BA 1) B
2) CB  2) CB  2) C  ()
3) C 3) C 3) C
4) A
Deci formula, după adăugarea lui A, este irealizabilă. Am aplicat
teoremele:
1. G este consecinţa logică a formulelor P1, P2, …, Pn atunci, şi numai
atunci, când P1  P2  …  Pn G este irealizabilă.
2. O mulţime L de clauze este irealizabilă dacă şi numai dacă există o
deducţie a clauzei vide din L.

1.2. Unificare
Pentru a aplica principiul rezoluţiei asupra predicatelor e necesar ca ele
în prealabil să fie unificate.
Unificator a doi termeni numim o substituţie care transformă aceşti
termeni în termeni identici.
Dacă, de exemplu, considerăm:
1) P  L(a)
2) Q  L(x),
atunci le putem unifica prin substituţia {a/x}.
În general, pentru a le unifica e necesar să aplicăm mai multe substituţii.
Considerăm 1) C1  L( a, x, f(g(y)) )
2) C2  L( z, f(z), f(u) ).
Atunci pentru a le unifica aplicăm substituţiile:
1. {a/z}  1) C1  L( a, x, f(g(y)) )
2) C2  L( a, f(a), f(u) )
2. {f(a)/x}  1) C1  L( a, f(a), f(g(y)) )
2) C2  L( a, f(a), f(u) )

5
3. {g(y)/u}  1) C1  L( a, f(a), f(g(y)) )
2) C2  L( a, f(a), f(g(y)) ).
Notăm mulţimea de substituţii {t1/x1, t2/x2, …, tn/xn} prin . Dacă pentru
predicatul L se aplică substituţia , variabilele x1, x2, …, xn din L se înlocuiesc
cu termenii t1, t2, …,tn. Rezultatul substituţiei îl notăm prin L. Presupunem că L
este o mulţime de formule: L={L 1, L2, …, Lm}. Dacă există o substituţie , astfel
încât L1=L2=…=Lm, atunci  se numeşte unificator pentru {L1, L2, …, Lm}.
Pentru exemplul de mai sus avem:
 = {a/z, f(a)/x, g(y)/u}.
Pentru una şi aceeaşi mulţime de formule pot exista mai mulţi
unificatori. Pentru {L(x, y), L(z, f(x))} substituţia 1 = {x/z, f(x)/y}, precum şi
substituţia 2 = {a/x, a/z, f(a)/y} sunt unificatori.
Dacă  şi  sunt două substituţii, atunci , de asemenea, este o
substituţie. Dacă pentru o mulţime de formule există o substituţie , astfel încât
celelalte substituţii pot fi exprimate prin , atunci substituţia  este cel mai
general unificator.
În procesul de căutare a unificatorului general, aranjăm predicatele într-o
anumită ordine şi examinăm termenii. Dacă un termen este x, iar altul este t şi
x nu se conţine în t, atunci se efectuează substituţia {t/x}.
Exemplu. 1) L(a, t, f(z))
2) L(a, x, z)
Aici primii termeni coincid. Pentru al doilea termen putem efectua
substituţia {t/x}, întrucât x nu se conţine în t. Pentru al treilea termen nu putem
efectua o astfel de substituţie, deoarece z se conţine în f(z). Deci, aceste
formule nu pot fi unificate.

1.3. Prolog şi programarea logicii predicatelor


Principiul rezoluţiei se aplică la formele normale disjunctive care au
forma: A1  A2  … An  B1  B2  … Bm.

6
Fiecare formulă bine formată poate fi adusă la forma normală
disjunctivă.
Considerăm formula: A  B  C  D  E. Ea poate fi scrisă astfel:
(B  D  E)  (A  C).
Aplicând legile lui de Morgan obţinem:
 (B & D & E)  (A  C).
În continuare aplicăm echivalenţa P  Q  P  Q şi obţinem:
B & D & E  (A  C).
Această formulă o scriem astfel:
B&D&E  A
B&D&E  C
Astfel de formule, care sunt clauze Horn, în Prolog se scriu:
A :– B, D, E.
C :– B, D, E.

1.4. Ce este un program Prolog?


Limbajul Prolog, spre deosebire de limbajele algoritmice, este un limbaj
descriptiv. Un program Prolog este un ansamblu de proprietăţi şi relaţii dintre
obiectele universului problemei. Executarea unui program constă în deducţia
unor relaţii ce rezultă din afirmaţiile programului. Problema constă dintr-o
mulţime de întrebări despre anumite obiecte.
Programarea în Prolog include următoarele etape:
- Se declară unele afirmaţii (fapte) despre obiecte şi relaţiile dintre ele.
- Se descriu regulile care definesc relaţiile dintre obiecte.
- Se formulează una sau mai multe întrebări (interogări, goal).
Să examinăm o secvenţă de program Prolog:
% Fapt: Ion este parintele lui Ada.
parinte(ion, ada).
parinte(maria, dan).
parinte(andrei, ana_maria).
parinte(maria, suzana).

7
acelasi_parinte(X,Y):–parinte(P,X),
parinte(P,Y).
Prima linie este comentată. Următoarele patru linii sunt fapte. Ultimele
două linii descriu o regulă, care afirmă că X şi Y au acelaşi părinte, dacă P
este părintele lui X şi a lui Y. Identificatorii X , Y şi P sunt variabile.
Simbolul % se utilizează pentru comentarii pe o linie.
Dacă în fereastra de dialog formulăm întrebarea:
Goal: acelasi_parinte(dan,suzana)
atunci răspunsul va fi: Yes
Pentru: Goal: acelasi_parinte(dan,ada)
răspunsul va fi: No
iar pentru: Goal: acelasi_parinte(suzana,Z)
răspunsul va fi:
Z=dan
Z=suzana
2 Solutions

Fig.1. Programul “acelasi_parinte” şi ferestre Turbo Prolog.

8
În fig.1 găsim conţinutul ferestrelor Turbo Prolog pentru programul
“acelasi_parinte”. Semnificaţia secţiunilor domains, predicates şi clauses le vom
examina mai târziu.
Să examinăm cum sistemul stabileşte răspunsul pentru ultima interogare
(goal). Pentru ca predicatul acelasi_parinte(X,Y) să fie adevărat, e necesar ca
predicatele parinte(P,X) şi parinte(P,Y) să fie adevărate.
De la început se suprapun predicatele acelasi_parinte(suzana,Z) din
goal cu acelasi_parinte(X,Z) din clauză (regulă). În rezultat X se
concretizează cu "suzana", iar variabila Y se leagă cu variabila Z. La
următorul pas se încearcă să se concretizeze parinte(P,X) pentru X="suzana"
cu baza de cunoştinţe şi P se concretizează cu "maria". Apoi se încearcă
concretizarea parinte(P,Y) pentru P="maria" şi se reuşeşte pentru Y="dan".
Se obţine prima soluţie: Z="dan". Deci s-a reuşit unificarea formulei din goal
cu o regulă.
Dacă goal din fereastra de dialog conţine variabile, atunci sistemul
caută toate soluţiile. În rezultat predicatul parinte(maria,Y) se suprapune cu
faptul parinte(maria,suzana) şi obţinem a doua soluţie: Y="suzana".
Predicatul acelasi_parinte în logica predicatelor poate fi scris:
X, Y (P (parinte(P,X) & parinte(P,Y) )  acelasi_parinte(X,Y) ).

Exerciţiu.
Pentru programul din fig.1 utilizaţi meniul şi selectaţi Options/Compiler
directives/Trace şi stabiliţi Trace sau Shorttrace. Treceţi la Run. Introduceţi în
fereastra Dialog
Goal: acelasi_parinte(suzana,Z)
Acţionaţi o singură dată tasta Enter. Apoi cu tasta F10 urmăriţi pas cu pas
executarea programului.

9
2. Elemente constitutive
2.1. Tipuri de date
Există mai multe tipuri standard. Noi vom defini tipurile cele mai uzuale.
Celelalte vor fi definite pe parcurs.
char – un caracter între apostrofuri;
symbol – şir de litere, cifre, şi “_“ , primul caracter fiind o
literă mică sau şirul este cuprins între ghilimele;
string – şir de caractere cuprins între ghilimele;
integer – număr întreg între –32768 … 32767;
real – un număr real cuprins între 1e-307 şi 1e+308.
Exemple.
char: ‘A’, ’a’, ’5’,’!’, ‘+’
symbol: x1, ”X1”, ion, ”Prolog”, “Turbo Prolog”
string: “x1”, ”X1”, “ion”, ”Prolog”, “Turbo Prolog”, “Ada”, ”+”
integer: 5, -35, 0
real: 5.0, -2.5, 2.6e+4
Pentru datele de tipul symbol, spre deosebire de tipul string, se creează
un tabel care se păstrează în memoria internă şi deci căutarea este mai rapidă.

2.2. Variabile. Constante


Variabila este un identificator care începe cu o majusculă şi reprezintă un
obiect necunoscut. Variabila cu numele “_” se numeşte variabilă anonimă.
Prezenţa variabilei anonime pe locul unui argument indică că nu ne interesează
valoarea legată de argument, ci numai existenţa argumentului.
Exemple. X, Z, Z1, _, An_nastere, List.
Variabilele nu se declară. Domeniul de vizibilitate a unei variabile este o
singură clauză (în care se utilizează). Limbajul Prolog nu utilizează conceptul de
atribuire. Transmiterea de valori pentru variabile se face pe baza unei legături
care se stabileşte între valoare şi variabilă.

10
Constantele sunt nume proprii şi reprezintă obiecte particulare ale
universului problemei.
Exemple. ana_maria, “Ana-Maria”, 123, 3.14, carte, triunghi.

2.3. Clauze
Clauzele sunt relaţii care au forma:
T :- Q1, Q2, …, Qn. (1)
În Turbo Prolog se poate scrie şi astfel:
T if Q1 and Q2 and … and Qn.
Simbolurile "…" sunt simboluri ale metalimbajului. T este capul clauzei
şi este un literal pozitiv. Q1, Q2, …, Qn – formează corpul clauzei şi sunt literali
negativi.
Dacă {Q1, Q2, …, Qn.} şi T, atunci clauza (1) este o afirmaţie
condiţionată (este o regulă).
Dacă {Q1, Q2, …, Qn.}= şi T, atunci clauza (1) este un fapt.
Dacă {Q1, Q2, …, Qn.} şi T=, atunci clauza (1) este o întrebare (o
interogare, un goal).

2.3.1. Fapte
Un fapt este un adevăr necondiţionat şi specifică o afirmaţie despre o
anumită relaţie. Relaţiile pot fi unare, binare etc.
Forma generală este:
nume_predicat(arg1, arg2, …, argm). (m  1)
Numele unui predicat este un identificator care începe cu o literă mică.
Argumentele sunt constante. Faptul este urmat de punct.
Exemple.
Faptul
plus(3,4,7).
descrie relaţia "3 plus 4 este egal cu 7".

11
Faptul
om(stefan).
reprezintă relaţia "Ştefan este om".
barbat(stefan). % Stefan este barbat
parinte(ion, ana). % Ion este parintele Anei
prefera(ana,aur). % Ana prefera aurul
varsta(ion,45). % Ion are 45 ani
parinti(ion,ada,mihai). % Parintii lui Mihai
% sunt Ion si Ada
adresa(mihai,104,victoriei,chisinau). % Adresa
% lui Mihai

2.3.2. Reguli
Regulile sunt afirmaţii adevărul cărora este determinat de către adevărul
mai multor factori. Regulile sunt nişte raţionamente (deducţii).
Forma generală a regulilor este:
nume_predicat(arg1, arg2, …, argn) :–
nume_predicat1(arg11, arg12, …, arg1m),
nume_predicat2(arg21, arg22, …, arg2l),
................................,
nume_predicatK(argk1 ,argk2, …, argkp).

Numărul argumentelor din paranteze poate fi şi nul. În acest caz


parantezele se omit. Predicatele pot conţine variabile.
Exemplu.
om(socrate). % fapt
muritor(X):–om(X). % X este muritor daca X
% este om
fiu(X,Y):–parinte(Y,X),barbat(X).
% X este fiul lui Y daca Y este
% parintele lui X si X este barbat.

Regula poate avea mai multe variante.


Exemplu.
intunecat(X) :– negru(X).
intunecat(X) :– brun(X).

12
Se afirmă că X este întunecat dacă X este negru sau brun. Această regulă
poate fi scrisă şi astfel:
intunecat(X):-negru(X);brun(X).

2.3.3. Interogări (goal)


După ce s-au declarat faptele şi regulile, poate fi declarat şi scopul. Ceea
ce se caută se numeşte în Prolog goal (interogare, scop). Scopul determină dacă
are loc o anumită relaţie dintre obiecte.
Forma unui goal coincide cu forma corpului unei reguli. Un goal poate fi
exterior sau interior. În cazul când goal este exterior (se introduce în fereastra de
dialog) se caută toate soluţiile, dacă el conţine variabile. Dacă nu conţine
variabile, atunci se caută o singură soluţie (prima). În cazul când goal este în
interiorul programului (declarat) se caută o singură soluţie (prima).
În procesul de căutare a soluţiilor Prolog încearcă să unifice predicatele
ce trebuie demonstrate (goal) cu un predicat declarat ca fapt sau deductibil.
Unificarea are loc atunci când:
- predicatele au acelaşi nume;
- obiectele unificate au acelaşi nume;
- obiectele şi valoarea variabilelor legate sunt identice;
- valorile a două variabile sunt identice;
- variabilele ce nu sunt legate se leagă la valorile obiectelor care se
unifică;
- variabila anonimă nu ia valoare, se unifică cu orice;
- dacă două variabile sunt legate, atunci îndată ce una este concretizată
(primeşte valoare) cealaltă e legată de aceeaşi valoare.

Exemple.
Presupunem o secvenţă de program:
are(ana,carte).
are(ana,masina).
are(dan,calculator).

13
are(dan,masina).
Această secvenţă declară că:
"Ana are carte, Ana are maşină, Dan are calculator, Dan are maşină".
Interogarea vom realiza-o în fereastra de dialog. Dacă formulăm scopul:
Goal: are(ana,masina)
atunci răspunsul va fi: YES, întrucât scopul este satisfăcut de către baza de
cunoştinţe. Dacă formulăm scopul:
Goal: are(ana,calculator)
atunci răspunsul va fi: NO.
La interogarea:
Goal: are(ana,X)
se răspunde:
X=carte
X=masina
2 Solutions
Cele două soluţii corespund celor două fapte din baza de cunoştinţe care
au primul argument identic cu obiectul ana. Pentru satisfacerea acestui scop se
parcurge baza de cunoştinţe. Se începe cu primul fapt: are (ana, carte) şi X se
leagă cu obiectul carte şi obţinem prima soluţie. Apoi se examinează restul
faptelor şi X se leagă şi cu obiectul masina. Deci avem două soluţii.
Admitem că ne interesează ce obiecte au şi Ana, şi Dan. Pentru aceasta
formăm:
Goal: are(ana,X),are(dan,X)
Răspunsul va fi: X=masina
1 Solution

2.4. Predicate aritmetice


În Prolog necesitatea utilizării expresiilor aritmetice este neesenţială
întrucât Prolog este orientat spre prelucrarea simbolică a informaţiei. Sintaxa
expresiilor aritmetice nu diferă cu mult de sintaxa expresiilor care se utilizează
în limbajele algoritmice, de exemplu Pascal. În Turbo Prolog operatorii

14
aritmetici sunt: + , – , * , / , div, mod. Ordinea de efectuare a operaţiilor este cea
clasică. Operanzii pot fi numere sau variabile. Expresia este calculabilă dacă
toate variabilele ce apar în ea sunt legate.
În Prolog se utilizează funcţiile:
X mod Y, X div Y, round(X), trunc(X), abs(X), cos(X), sin(X), tan(X),
arctan(X), exp(X), ln(X), log(X), sqrt(X).
random(X) – X ia o valoare generată aleator: 0  X < 1.
Operatori relaţionali sunt: <, <=, =, >=, >, <>. Aceşti operatori pot
compara nu numai expresii aritmetice dar şi caractere. Operatorul "=" este
utilizat nu numai ca operator relaţional, dar şi ca operator de atribuire.
În dreapta operatorului "=" poate fi o expresie, iar în partea stângă poate
fi o variabilă sau o constantă. Dacă variabila este legată, atunci valoarea ei se
compară cu valoarea expresiei din dreapta. Dacă variabila nu este legată, atunci
ea se concretizează cu valoarea expresiei. Presupunem că N este variabilă liberă,
atunci termenii N=5, N=5, generează true. Primul termen N=5 concretizează N,
iar al doilea N=5 este true, întrucât N este concretizată cu 5.
Termenii N=5, N=6 generează un rezultat fals, întrucat 5<>6. În Prolog
nu putem scrie expresii de forma N=N+1.

2.5. Intrări. Ieşiri


În acest compartiment examinăm unii operatori de intrare/ieşire. Alţi
operatori îi vom examina pe parcurs. Întrucât în Prolog variabilele nu sunt
declarate operatorii de intrare diferă de cei ce se utilizează în limbajele
algoritmice.
Predicate de intrare:
1. readln(String) % Citeşte o linie de text, terminată cu Enter.
2. readint(Integer)
3. readchar(Char)
4. readreal(Real)

15
Aici am utilizat un mod de descriere a operanzilor pe care îl vom utiliza
şi pe parcurs. În paranteze am indicat că argumentul este o variabilă (primul
caracter este o majusculă) şi tipul ei.
Cu operatorul write putem afişa orice tip de obiecte.
Formatul general:
write(arg1, arg2, …, argn)
Argumentele sunt obiecte. Operatorul poate fi folosit atât în reguli, cât şi
în goal.
Exemplu. Goal: muritor(X),write(X)
sau Goal: muritor(X),write("muritor:",X),nl
unde nl este un operator de ieşire şi are semnificaţia ‘\n’ din alte limbaje. Se
citeşte linie nouă (newline).
Predicatul writef este analog lui write, în plus are un format conform
căruia are loc afişarea. Formatul general este:
writef(<format>, arg1, arg2, ..., argn)
<format> este un şir de caractere în care se specifică formatul pentru
fiecare argument. Acest şir trebuie să conţină atâţia specificatori de format, câte
argumente sunt indicate. Specificatorul formatului are forma generală:
%[-] m.pw
unde:
- – determină alinierea la stânga (implicit la dreapta);
m – este numărul maxim de poziţii;
p – este numărul maxim de poziţii pentru partea zecimală;
w – poate fi:
f – real în zecimal fixat;
e – real în notaţie exponenţială;
g – real scurt (implicit);
s – string sau symbol;
w poate fi omis.

16
Exemplu. Admitem că X=1.0, Y=3.37, Z=4.5
writef("Sunt numere %1 si %4.1, precum si %-10e",X,Y,Z).
Răspunsul va fi:
Sunt numere 1 si 3.4, precum si 4.5e+00
Exerciţiu.
Considerăm exemplul din compartimentul 2.3.3.
Care vor fi răspunsurile sistemului pentru următoarele scopuri:
Goal: are(_,carte)
Goal: are(_,aur)
Goal: are(X,Y)
Goal: are(ana,X), are(dan,Y)

17
3. Structura programelor Turbo Prolog
Un program Prolog este divizat în secţiuni. Secţiunile Prolog sunt:
- domains
- global domains
- database
- predicates
- global predicates
- clauses
- goal
Unele secţiuni putem să le omitem, dacă ele nu se utilizează. De
exemplu, dacă nu utilizăm bazele de date dinamice, atunci secţiunea database
nu se include în program.
În secţiunea domains se includ tipurile de obiecte (domeniul obiectelor)
definite de utilizator. Secţiunea domains arată astfel:

domains
lista_nume_dom_def1 = tip1
lista_nume_dom_def2 = tip2
........................

unde lista_nume_dom_def este o listă din unul sau mai multe domenii ce se
definesc la fel. Numele domeniului este un identificator. Există mai multe
moduri de definire a domeniilor:
1. dom = d
unde dom este numele domeniului, iar d este unul dintre tipurile: char,
symbol, string, integer, real.
Exemplu.
s = symbol
nume, prenume, oras = string
2. dom_list = d*
unde d este un tip definit anterior sau un tip standard (predefinit).

18
Exemplu.
s = symbol % nu este o lista
ls = s* % lista de simboluri
dom_lista_i = integer* % lista de intregi
3. domeniu_compus = functor1(d1, d2, …);
functor2(d3, d4, …);
................
functork(dk, dk+1, …)
După functork nu urmează ' ; ' , iar d1, d2, …, d3, d4, …, dk, dk+1 sunt
domenii definite sau tipuri standard. Pentru functor2,… functork domeniul
domeniu_compus este de acum definit. Domeniile pot fi definite şi recursiv.
Exemple.
i = integer
dom1 = int(i) ; s(symbol)
Menţionăm că după definiţii nu se pune punct. Definiţiile se separă prin
spaţii.
Limbajul Turbo Prolog este puternic tipizat. O variabilă sau un obiect
care este de un tip oarecare nu poate apărea pe poziţii unde se cere alt tip.
Excepţie fac doar câteva tipuri standard pentru care conversia se face automat.
Tipurile definite de utilizator nu pot fi echivalate unul cu altul.
Dacă în secţiunea domains avem:
domains
nume, prenume = symbol
. . . . . . . . . . . .
atunci tipurile nume şi prenume sunt diferite.
Conversiile automate admise sunt:
- între string şi symbol;
- între integer, char şi symbol (char este reprezentat ca un cod ASCII).

Secţiunile domains şi predicates pot fi declarate globale:


global domains
global predicates

19
Definiţiile scrise pot fi incluse în unul sau mai multe programe cu:
include “nume_fisier_dos”
care se scrie la începutul programului.
În secţiunea database se declară predicatele din baza de date dinamică.
Modul de definire este acelaşi ca şi pentru secţiunea predicates. În această
secţiune se defineşte baza de date dinamică. Numele predicatelor definite în
secţiunea database trebuie să difere de cele din secţiunea predicates. Baza de
date dinamică în Turbo Prolog poate să conţină numai fapte.
În secţiunea predicates se declară structura tuturor predicatelor din
program. Forma generală:
nume_predicat(d1, d2, …)
unde d1, d2, … sunt domeniile argumentelor ce sunt definite anterior sau
standarde.
Exemple.
domains
s = symbol
ls = s*
predicates
element(s, ls)
virsta(s, integer)
start % nu contine argumente
Declaraţiile se separă prin spaţii.
Secţiunea clauses conţine baza de cunoştinţe. În această secţiune se
scriu faptele şi regulile.
În secţiunea goal se conţine o interogare (scopul). Dacă această
secţiune lipseşte, atunci interogările se fac din fereastra de dialog.
În programul din fig.2 sunt incluse toate secţiunile necesare.
domains
s=symbol
predicates
acelasi_parinte(s,s)
femeie(s) barbat(s)
sora(s,s)
parinte(s,s)
clauses

20
parinte(ion,ada).
% Ion este parintele Adei
parinte(petru,ion).
parinte(petru,maria).
parinte(suzana,ion).
parinte(ion,angela).
parinte(suzana,maria).
parinte(andrei,ion).
femeie(ada).
femeie(maria).
femeie(suzana).
femeie(angela).
barbat(ion).
barbat(petru).

acelasi_parinte(X,Y):–parinte(P,X),
parinte(P,Y), X<>Y.
sora(A,B):–acelasi_parinte(A,B), femeie(A).
goal sora(X,Y),write(“Surori:”,X,” si ”, Y).

Fig.2. Programul “Părinţii”.

Comentarii
Comentariile pot fi incluse între caracterele /* şi */ . Caracterul %
poate fi utilizat pentru comentariile de pe o linie.
Exerciţii.
1. Pentru programul din fig.2, în fereastra de dialog, să se formuleze
următoarele goal-uri:
Goal: acelasi_parinte(ion, X)
Goal: sora(X, ada)
Goal: acelasi_parinte(maria, Y), barbat(Y)
Goal: sora(X, Y)
Să se explice răspunsurile obţinute.
2. Să se dezvolte programul din fig.2 pentru a determina care dintre
persoane sunt veri, sau verişoare.

21
4. Procesarea programelor Prolog
4.1. Căutarea soluţiilor
Căutarea soluţiilor se face prin potrivire (unificare), Prolog încercând să
unifice fiecare predicat pentru demonstrare cu un predicat declarat ca fapt sau
deductibil Prolog.
Dacă nu se reuşeşte potrivirea (unificarea), Prolog se întoarce (revine) şi
încearcă altă legare a variabilelor pentru ultima clauză sau o altă variantă de
evaluare a clauzelor.
În continuare să urmărim procesul de căutare a soluţiilor printr-un
exemplu:
domains
i = integer
predicates
cifra(i)
numar1
numar2(i, i, i)
clauses
cifra(0).
cifra(1).
numar1:–cifra(A),cifra(B),cifra(C),
write(A, B, C), nl.
numar2(A,B,C):–cifra(A),cifra(B),cifra(C),
write(A, B, C), nl.

Pentru un goal exterior: Goal: numar1


răspunsul va fi: 000
1 Solution
iar pentru: Goal: numar2(X, Y, Z)
răspunsul va fi: 000
001
010
011
100
101
110
111
8 Solutions

22
Primul goal va genera o singură soluţie, deoarece nu conţine variabile.
Al doilea goal va genera toate soluţiile, întrucât conţine variabile. Să urmărim
cum se obţin aceste soluţii.
Variabilele X, Y şi Z se leagă cu variabilele A, B şi C. Predicatele
cifra(A), cifra(B) şi cifra(C) se unifică cu cifra(0) şi obţinem prima soluţie. Se
caută următoarea soluţie (a doua). Pentru aceasta cifra(C) se unifică cu cifra(1).
Pentru cifra(C) s-au epuizat toate variantele de unificare. În acest caz se revine
la cifra(B), care se unifică cu cifra(1), iar cifra(C) se unifică cu cifra(0) şi
cifra(1). Se obţin soluţiile:
010
011
După aceasta, deoarece pentru cifra(B) şi cifra(C) s-au epuizat toate
variantele, se revine la cifra(A) şi astfel se obţin celelalte soluţii.
Procesul de căutare a soluţiilor pentru numar2(…) poate fi reprezentat
printr-un graf (fig.3).

numar2

cifra(A) -----> 0 1

cifra(B) -----> 0 1 0 1

cifra(C) -----> 0 1 0 1 0 1 0 1

Fig. 3. Căutarea soluţiilor.

Deci, Prolog caută soluţiile după principiul “căutare în profunzime”.

23
4.2. Scopuri deterministe şi nondeterministe. Predicatele fail, cut,
not
Un scop este determinist dacă el oferă o singură soluţie. În caz contrar se
spune că scopul este nondeterminist. Pentru goal numar1 avem determinism, iar
pentru goal numar2(A,B,C), când goal este exterior, avem nondeterminism.
Un program poate fi impus să genereze toate soluţiile. Şi invers, un
program nondeterminist poate fi impus să genereze o singură soluţie. Deci, să
devină determinist.
Predicatul fail
Predicatul fail (eşec, nereuşită) se foloseşte pentru a impune un
program să fie nondeterminist. Acest predicat niciodată nu se acordă cu baza de
cunoştinţe şi returnează valoarea false. Acest fapt impune sistemul să revină şi
să caute alte soluţii, deci generează backtracking. Soluţiile găsite trebuie
folosite înainte de fail. În reguli după fail nu are sens să urmeze alte predicate,
întrucât fail nu se acordă cu baza de cunoştinţe.
Considerăm secvenţa de program:
cifra(0). cifra(1).
numar3:-cifra(A),cifra(B),cifra(C),
write(A,B,C),nl,fail.

numar4:-cifra(A),cifra(B),cifra(C),
write(A,B,C),nl,fail.
numar4.
Pentru un goal exterior:
Goal: numar3
se vor afişa toate soluţiile şi No, pe când:
Goal: numar4
va afişa toate soluţiile şi YES.
Atât numar3, cât şi numar4 generează soluţiile până la fail. După ce se
găsesc toate soluţiile, fail returnează false şi sistemul caută alte exemplare
pentru numar3 (numar4). Pentru numar4 există o astfel de regulă
necondiţionată:

24
numar4.
care se acordă cu baza de cunoştinţe şi răspunsul este YES.
Predicatul cut
Predicatul cut (tăietură, reducere) care în program se scrie "!", împiedică
revenirea. Acest predicat se utilizează atunci când este necesar să reducem
spaţiul de căutare a soluţiilor. Deci, putem evita căutările inutile ale soluţiilor.
Predicatul cut se utilizează şi în cazul când dorim să reducem numărul de
soluţii.
Considerăm o secvenţă de program:
cifra(0). cifra(1).
numar5(X,Y,Z):-cifra(X),!,cifra(Y),cifra(Z),
write(X,Y,Z),nl.
Atunci un goal exterior:
Goal: numar5(X,Y,Z)
va genera soluţiile: 000
001
010
011
Yes
Dacă în procesul de căutare a soluţiilor se trece în dreapta lui "!", atunci
se caută toate soluţiile din dreapta şi nu se revine în stânga lui "!". Se poate
spune că "!" face o tăietură în graful din fig.3. Subgraful din dreapta se exclude
din spaţiul de căutare a soluţiilor. Deci am redus numărul de soluţii.
Considerăm:
p :– a(X), b(Y), !, c(X, Y, Z).
p.
Dacă a(X) şi b(Y) se acordă cu baza de cunoştinţe, atunci se trece în
dreapta lui "!" şi se încearcă concretizarea predicatului c(X,Y,Z). În acest caz nu
se revine nici la a(X), nici la b(Y), chiar dacă c(X,Y,Z ) nu se acordă cu baza de
cunoştinţe şi nici la un alt p.
Dacă avem regulile:
p :– !, q1.
p :– q2.

25
atunci a doua regulă este de prisos.
Exemple.

1, daca x  2
Admitem funcţia: f (x)  0, daca x  2
 1, daca x  2
În Prolog se poate scrie:
% varianta 1
f(X,Y) :– X>2, Y=1.
f(X,Y) :– X=2, Y=0.
f(X,Y) :– X<2, Y=-1.
Pentru un goal exterior:
Goal: f(7,Z)
Se răspunde Z=1
1 Solution
Însă, după ce soluţia a fost găsită, s-au căutat şi alte soluţii. S-a încercat
unificarea cu regula a doua şi a treia.
Acest predicat mai poate fi scris:
% varianta 2
f(X,Y) :– X>2, Y=1,!.
f(X,Y) :– X=2, Y=0,!.
f(X,Y) :– X<2, Y= -1,!.
sau
% varianta 3
f(X,1) :– X>2,!.
f(2,0) :– !.
f(_,-1) :– !.

26
Pentru goal: f(7,Z) sistemul nu examinează a doua şi a treia regulă.
Astfel se evită căutările inutile. Este preferabilă varianta 2, întrucât varianta 3
poate genera răspunsuri greşite pentru unele goal-uri (de exemplu, pentru
Goal: f(7,-1) se va răspunde Yes).
Considerăm predicatul maximum(X,Y,Z), unde Z este valoarea maximă
dintre X şi Y. Acest predicat poate fi definit astfel:
maximum(X,Y,X):-X>=Y,!.
maximum(X,Y,Y).

Acest predicat stabileşte: "Dacă X>=Y, atunci X este maximum. În caz contrar
maximum este Y". Această definiţie poate genera erori. De exemplu scopul:
Goal: maximum(3,2,2)
este satisfăcut.
Eroarea rezultă din faptul, că în regula a doua unificarea se face în mod implicit.
Varianta corectă a predicatului este:
maximum(X,Y,X):-X>=Y,!.
maximum(X,Y,Y):-X<Y.

Aici unificarea se face în mod explicit.


Predicatul not
Predicatul not are forma:
not(predicat)
Presupunem că predicatul este P(X). Dacă P(X) se acordă cu baza de cunoştinţe,
not(P(X)) nu se acordă. Dacă P(X) nu se acordă (este fals), atunci not(P(X))
se acordă cu baza de cunoştinţe. Predicatul not poate fi definit astfel:
not(P(X)):-P(X),!,fail.
not(_).

Predicatul P(X) nu trebuie să conţină variabile nelegate, însă poate să


conţină variabile anonime.

4.3. Recursivitate. Simulări de cicluri

27
În programarea logică recursia este deosebit de importantă, întrucât
lipsesc construcţii de forma while … do … şi repeat … until .
În Prolog ciclurile, de regulă, se simulează utilizând fail sau prin
recursivitate. Recursia se utilizează efectiv la interogarea bazelor de date, la
prelucrarea listelor şi în alte cazuri.
Un predicat recursiv trebuie să conţină o condiţie de limită (de finalizare)
a recursiei.
Să examinăm relaţia stramos, care poate fi definită astfel: părinţii sunt
strămoşi, părinţii părinţilor sunt strămoşi etc.
În Prolog această relaţie o putem defini astfel:
stramos(X,Z) :– parinte(X,Z).
stramos(X,Z) :– parinte(Y,Z), stramos(X,Y).
Predicatul stramos poate fi reprezentat grafic astfel:

stramos

X ... Y Z
parinte
stramos
Să examinăm modul de tratare a predicatelor recursive.
Admitem funcţia n! . Notăm această funcţie prin f , atunci:
f(0) = 1 ,
f(n) = n * f(n-1) , n>0.
Această funcţie în Prolog poate fi definită astfel:
factorial(0,1):–!.
factorial(N,F):–N1=N-1,factorial(N1,F1),F=N*F1 .
Să urmărim cum sunt tratate predicatele recursive de către sistem. În
acest scop utilizăm noţiunea de stivă. Aici nu examinăm modul de realizare a
predicatelor recursive în sistem. Noţiunea de stivă se utilizează ca o modalitate
de descriere a procesului de satisfacere a scopului (goal).
Examinăm scopul: Goal: factorial(3,F)

28
Se încearcă unificarea scopului cu regula factorial(0,1). Nu se reuşeşte.
Se trece la a doua regulă care generează subscopul factorial(2,F1). Apoi se
generează subscopul factorial(1, F1' ) şi factorial(0, F1'') .
Subscopul factorial(0, F1'') se unifică cu prima regulă şi F1'' se
concretizează cu valoarea 1. Acest proces îl putem descrie cu ajutorul unei stive
(fig.4), care conţine cortegii de forma < N, F >. De la început în stivă se pune
< 3, F >, unde 3 este valoarea legată de N şi F este variabilă liberă. Stiva se
trece într-o nouă stare şi se pune < 2, F1 >. Analog se pune în stivă < 1, F1' >.
Subscopul factorial(0, F1'') se unifică cu prima regulă şi în stivă se pune
< 0, 1 >. Acum poate fi examinat subscopul F = N * F1.

0 1 
1 F1' 1 1 
2 F1 2 F1 2 2 
 3 F 3 F 3 F 3 6 
< N, F >
a) b) c) d)
Fig. 4. Stiva pentru scopul factorial(3, F).

Acest scop poate fi interpretat astfel: "Să se ia F1 din vârful stivei şi să


se treacă cu o poziţie mai jos, de unde se ia N şi să se calculeze F pentru
această poziţie". De la starea a) a stivei (fig.4.) se trece pe rând la stările b), c)
şi d). Se poate spune că au loc atribuirile:
F1' = 11, F1 = F1'2, F = F1*3.
Prima regulă a predicatului factorial se termină cu cut(!). Predicatul
cut(!) e necesar în caz de nondeterminism. Dacă el nu va fi prezent în prima
regulă, sistemul va încerca căutarea altor soluţii şi va trece la valori negative
pentru N, ceea ce nu e corect.
Predicatul factorial nu este lipsit de imperfecţiuni. Unificarea scopului
cu prima regulă se va încerca de (N+1) ori, pe când ar trebui încercat o singură
dată. Pentru un scop cu N negativ, predicatul va genera o recursie "veşnică".
Varianta corectă a predicatului factorial ar fi:

29
fact(N,F):–N>0,N1=N-1,fact(N1,F1),F=N*F1.
fact(0,1).
Pentru unele predicate putem utiliza iteraţia în loc de recursie. În
predicatul fact1 se foloseşte iteraţia pentru a calcula factorialul lui N.
predicates
fact1(integer, real)
fact1(integer, integer, real, real)
clauses
fact1(N,F):-fact1(0,N,1,F).
fact1(R,N,T,F):-R<N,R1=R+1,T1=T*R1,
fact1(R1,N,T1,F).
fact1(N,N,F,F).

4.4. Predicatul repeat


În unele versiuni ale limbajului Prolog acest predicat este predefinit. În
Turbo Prolog trebuie definit şi poate fi definit astfel:
repeat.
repeat:-repeat.
În unele cazuri scopurile pot genera o singură soluţie. Dacă este necesar ca
scopul să se repete, putem simula un ciclu "veşnic" cu predicatul repeat
provocând backtracking-ul.
Exemplu.
ecou:-repeat,readln(Nume),
write(Nume),caz(Nume),!.
caz(stop).
Exerciţii.
1. Comentaţi predicatele not şi ecou. Menţionăm că predicatele de
intrare/ieşire nu generează soluţii noi.
2. Baza de cunoştinţe conţine fapte de forma:
ruta(Loc1, Loc2, Ora_p, Ora_s).

30
care stabilesc ora plecării (Ora_p) din localitatea 1 (Loc1) şi ora sosirii (Ora_s)
în localitatea 2 (Loc2) a autobuzului. Să se scrie un program care găseşte căile
de la X la Y, astfel încât să se lase timp pentru transbordare.
3. Să se definească un predicat care pentru un număr natural n dat
n 1
calculează suma: S  .
i 1 i

4. Considerăm şirul Fibonacci. Să se definească predicatul fib(N,R) care


stabileşte elementul R de pe locul N, dacă N este concretizat.
5. Să se definească în Prolog funcţia Ackermann:

m  1 , daca n  0
A(n, m)   A(n  1, 1) , daca n  0 , m  0
 A(n  1, A(n, m  1)) , daca n  0 , m  0

31
5. Prelucrări de obiecte
5.1. Liste
O listă reprezintă un ansamblu de obiecte de acelaşi tip, mai exact
obiectele aparţin unuia şi aceluiaşi domeniu. Numărul de elemente ale unei liste
nu este fixat. Elementele listei pot fi obiecte compuse. Se poate declara o listă de
liste. Elementele unei liste se separă prin virgule şi întreg ansamblul se include
între paranteze drepte.
Exemple de liste:
[1, 2, 3, 3, 10] – listă de întregi
[ [1,2], [3,3,10] ] – listă de liste de întregi
[] – listă vidă
Tipurile de liste sunt declarate în secţiunea domains sub forma:
nume_dom* .
Exemplu.
domains
nume_dom=real
list=nume_dom*
llist=list*
unde list este tipul listei şi specifică o listă de numere reale, iar llist specifică o
listă de liste.
Primul element al unei liste se numeşte capul listei. Restul elementelor
formează coada listei (tail). Coada este o listă care poate fi şi vidă. Capul se
separă de restul listei prin semnul "|". Menţionăm că semnul "|" nu elimină
elementul (capul listei) din listă.
Să exemplificăm separarea listei în cap şi rest:
Lista Cap Rest(tail)
[a, b, c] a [b, c]
[a] a []
[] nedefinit nedefinit
[[1,3], [8], [1,7]] [1,3] [[8], [1,7]]

32
În procesul de unificare o variabilă poate fi concretizată cu o listă sau cu
un element al listei.
Să exemplificăm unificarea a două liste:
Lista 1 Lista 2 Unificare
[X,Y,Z] [albastru, galben, rosu] X = albastru, Y = galben, Z = rosu
[5] [X | Y] X=5, Y=[ ]
[‘a’,’b’,’c’,’d’] [X, Y | Z] X=’a’, Y=’b’, Z=[‘c’,’d’]
[1,2] [3 | X] nu se unifică

5.2 Lucrul cu listele


Predicatele de prelucrare a listelor sunt recursive. În general aceste
predicate au forma:
procedure([ ]).
procedure([X|Rest]):–prelucrare(X),
procedure(Rest).
Prima regulă este condiţia de finalizare a recursiei. Deci, a prelucra o
listă înseamnă a-i prelucra toate elementele până când o epuizăm. Vom prezenta
câteva predicate de prelucrare a listelor.
1) Afişarea listei:
afisare([ ]).
afisare([X|Rest]):–write(X),afisare(Rest).
La interogarea
Goal: afisare([a,b,c])
răspunsul va fi:
abc
2) Afişarea listei în ordine inversă:
afisare_inv([ ]).
afisare_inv([X|Rest]):–afisare_inv(Rest),
write(X).
La interogarea
Goal: afisare_inv([a,b,c])
se afişează
cba

33
Iniţial elementele listei se pun într-o stivă şi atunci când finalizează
recursia, elementele se scot din stivă şi se afişează (write).
3) Lungimea listei (numărul elementelor dintr-o listă):
list_len([ ],0). %Lungimea liste vide este 0
list_len([_|R],N):–list_len(R,N1),N=N1+1.
La interogarea
Goal: list_len([a,b,c,d],X)
se răspunde
X=4
4) Suma elementelor (pentru elemente numerice):
% Suma elementelor unei liste vide este 0
sum_e([ ],0).
sum_e([X|R],S):- sum_e(R,S1), S=S1+X.
La interogarea
Goal: sum_e([1,3,7,-1],Z)
se răspunde
Z=10
5) Apartenenţa unui element la o listă: Dacă X este concretizat şi X
este element al listei Y, atunci predicatul member(X,Y) este adevărat, în caz
contrar este fals. Dacă X nu este concretizat şi scopul este determinist, atunci
X se concretizează cu primul element din lista Y. În cazul nondeterminismului
X se concretizează pe rând cu toate elementele din Y.
member(X,[X|_]).
member(X,[_|Y]):– member(X,Y).
La interogarea
Goal: member(b,[a,b,c])
se răspunde
Yes
La interogarea determinstă
Goal: member(X,[a,b,c])
se răspunde

34
X=a
La interogarea nondeterminstă
Goal: member(X,[a,b,c])
se răspunde
X=a
X=b
X=c
Utilizând predicatul member, putem verifica dacă un element aparţine
unei liste sau putem extrage elementele din listă.
Acest predicat, precum şi alte predicate de prelucrare a listelor, se
defineşte în dependenţă de tipul elementelor. De exemplu dacă member e
definit pentru tipul symbol, atunci pentru liste cu elemente integer trebuie
definit alt predicat.
6) Concatenarea a două liste:
append([ ], L, L).
append([X|L1],L2,[X|L3]) :– append(L1,L2,L3).
Prima regulă specifică faptul că o listă vidă concatenată la o listă L este
L. Această regulă este condiţia de finalizare a recursiei. A doua regulă extrage
câte un element (capul) din prima listă şi îl trece la a treia listă (la rezultat).
Alipirea lui X la L3 nu are loc până când L3 nu va fi concretizat. Deci iniţial
elementele X se pun în stivă şi se preiau atunci când L3 va fi concretizat de
către prima regulă.
La interogarea
Goal: append([a,b,c], [m,n], Z)
se răspunde
Z=[a,b,c,m,n]
În momentul concretizării variabilei Z cu [m,n] în stivă se conţine:
c 
b
a

35
După concretizarea lui Z s-a extras din stivă ‘c’ şi Z=[c,m,n], apoi s-a
extras ‘b’ şi Z=[b,c,m,n], şi, în sfârşit, s-a extras ‘a’ şi rezultatul final este
Z=[a,b,c,m,n].
În cazul când X, Y şi Z sunt concretizate, predicatul append(X,Y,Z)
este adevărat numai dacă lista Z coincide cu lista obţinută ca rezultat al
concatenării listelor X şi Y.
La interogarea
Goal: append([1,2], X, [1,2,7,8,9])
se răspunde
X=[7,8,9]
Pentru acest predicat argumentele pot fi toate concretizate. De regulă,
acest predicat se utilizează când două argumente sunt concretizate (legate) şi
unul neconcretizat (liber). Aceasta este o proprietate a predicatelor PROLOG,
care permit polimorfismul. Predicatul append(X,Y,Z) poate fi utilizat şi atunci
când X şi Y sunt libere, iar Z concretizat. Predicatul are prototipul: (i,i,i),
(i,i,o), (i,o,i), (o,i,i), (o,o,i), unde prin i se notează input (intrare) şi prin o –
output (ieşire).
7) Aflarea ultimului element din listă. Predicatul ultim_el(X,Y) este
adevărat numai dacă X este ultimul element al listei Y.
ultim_el(X,[R]):– X=R.
ultim_el(X,[_|Y]):– ultim_el(X,Y).
Acest predicat poate fi scris şi altfel:
ultim_el(X, [X]).
ultim_el(X, [_|Y]) :– ultim_el(X,Y).
La interogarea
Goal: ultim_el(E, [a,b,c])
se răspunde
E=c
8) Aflarea elementului de pe locul K din listă:
el_i([_|L],K,E):– K>1, K1=K–1, el_i(L,K1,E).
el_i([X|_], 1, X).

36
Predicatul el_i(L,K,E) este adevărat dacă lista conţine cel puţin K
elemente (K>0). Variabila E se concretizează cu elementul de pe locul K.
9) Inversarea ordinii elementelor unei liste:
invers_l(Inputlist,Ouputlist):–
reverse([ ],Inputlist,Ouputlist).

reverse(L1,[ ],L1).
reverse(L1,[X|L2],L3):– reverse([X|L1],L2,L3).
La interogarea
Goal: invers_l([1,2,3,4],R)
se răspunde
R=[4,3,2,1]
10) Eliminarea unui element din listă.
del(X,[X|L],L):– !.
del(X,[Y|L],[Y|L1]):– del(X,L,L1).
La interogarea
Goal: del(d, [a,b,c,d], Z)
se răspunde
Z=[a,b,c]
Predicatul este adevărat dacă se reuşeşte eliminarea.
11) Eliminarea tuturor apariţiilor unui element dintr-o listă.
del_all(X,[X|L],R):– del_all(X,L,R).
del_all(Z,[X|L],[X|R]):– X<>Z,del_all(Z,L,R).
del_all(X,[ ],[ ]).
La interogarea
Goal: del_all(b, [a,b,c,b], Z)
se răspunde
Z=[a,c]
12) Aflarea elementului maximal din listă.
maximum([X|L],M):– max_el(L,X,M).

max_el([X|L],Y,M):– X<=Y,max_el(L,Y,M).
max_el([X|L],Y,M):– X>Y,max_el(L,X,M).
max_el([ ],M,M).

37
La interogarea
Goal: maximum([2,3,7,4], Z)
se raspunde
Z=7
13) Intersecţia a două mulţimi. Admitem că L1 şi L2 sunt două liste care
nu conţin elemente ce se repetă (adică reprezintă două mulţimi). Predicatul
intersectie(L1, L2, L3) stabileşte în L3 elementele comune ale listelor L1 şi L2.
intersectie([ ],_,[ ]).
intersectie([X|R],Y,[X|Z]):– member(X,Y),!,
intersectie(R,Y,Z).
intersectie([X|R],Y,Z):– intersectie(R,Y,Z).
La interogarea
Goal: intersectie([a,b,c,d], [e,c,a], L)
se răspunde
L=[a,c]

5.3. Predicatul findall


Predicatul findall este predefinit şi are forma:
findall(Var, nume_predicat(…Var…), List)
Se obţine lista List, formată din toate valorile variabilei Var care rezultă
din unificarea predicatului dat peste o bază de fapte.
Considerăm programul:
domains
nume, disciplina = symbol
grupa, nota = integer
predicates
stud(nume, grupa, disciplina, nota)
clauses
stud(dan, 1, m, 10).
stud(andrei, 2, a, 9).
stud(maria, 1, f, 8).
stud(ion, 1, a, 10).
La interogarea:
Goal: findall(X, stud(X, 1, _, 10), L)
se răspunde

38
L=[dan,ion]
iar la interogarea:
Goal: findall(X, stud(_, _, _, X), L)
se raspunde
L=[10,9,8,10]
Variabila anonimă “_” se concretizează cu orice.

5.4. Sortarea listelor


La sortarea listelor vom utiliza metoda inserţiei. Această metodă este cea
mai potrivită pentru a fi descrisă în Prolog. Pentru programul “sortarea listelor”
scopul sortare(X,Ls) sortează elementele listei X în ordine crescătoare.
Rezultatul este Ls.
Predicatul sortare( ) pune elementele listei X în stivă. Ordonarea se
începe cu ultimele elemente din lista iniţială. Prima regulă insertie( ) asigură
înscrierea primului element în lista rezultat, precum şi a elementelor care în
procesul de ordonare trebuie să ocupe ultimul loc în sublistă. A doua regulă
insertie( ) caută locul elementului X printre elementele sublistei sortate pentru
ca a treia regulă să-l insereze.
domains
i=integer li=i*
predicates
sortare(li,li) insertie(i,li,li)
clauses
sortare([X|Xs],Ls):-sortare(Xs,Zs),
insertie(X,Zs,Ls).
sortare([],[]).

insertie(X,[],[X]).
insertie(X,[Y|Ls],[Y|Zs]):-X>Y,
insertie(X,Ls,Zs).
insertie(X,[Y|Ls],[X,Y|Ls]):- X<=Y.

Fig.5 Programul “Sortare”.


La interogarea Goal: sortare ([7,1,-1,4,3],Y)
răspunsul va fi Y=[-1,1,3,4,7]

39
La interogarea Goal: insertie (3,[1,2,4,5],Y)
răspunsul va fi Y=[1,2,3,4,5]

5.5. Obiecte compuse


Obiectele în Prolog pot fi atât simple, cât şi compuse. Un obiect simplu
poate fi un caracter, un număr sau un atom. Un atom poate fi un symbol sau
string. Un obiect compus are următoarea formă:
functor(arg1,arg2,...argn)
unde arg1, arg2, ..., argn sunt obiecte simple sau compuse. Un obiect compus
poate avea mai multe variante:
dom= functor1(arg1,arg2,...argm);
functor2(arg1,arg2,...argl);
………………………………
functork(arg1,arg2,...argk)
unde argi sunt obiecte simple sau compuse.
Exemplu.
domains
s = string
dom = s; carte(s,s,s); masina(s,s,s,s)
predicates
are(s,dom)
clauses
are(dan,ceas).
are(dan,carte(”Programarea in Prolog”,
”Clocksin W.”,”1984”)).
are(dan,masina(”Audi”,”100”,”1998”,”D”)).

Obiectele compuse pot fi definite şi recursiv.


Considerăm programul:
domains
i = integer
exp = o; s(exp)
predicates
natural(exp,i)
clauses

40
natural(s(o),1):-!.
natural(s(X),N):-natural(X,N1),N=N1+1.

La interogarea:
Goal: natural(s(s(s(o))),Z)
răspunsul va fi Z=3
Listele, de asemenea, pot conţine elemente compuse. Considerăm
domains
i = integer
s = symbol
dom_ex = ex(s,i); col(s,s)
l_ex = dom_ex*

şi
predicates
% reusita(nume,grupa,lista_exam_colocv.)
reusita(s,string,l_ex)

Atunci putem manipula cu termeni de forma:


reusita(ana,”I41”,[ex(algebra,10),col(geom, admis),ex(“baze de date”,9)])
Exerciţii.
1. Considerăm predicatele:
element1(X, [X|_]).
element1(X, [_|L]) :– element1(X,L), !.

element2(X, [X|_]) :– !.
element2(X, [_|L]) :– element2(X, L).

element3(X, [X|_]).
element3(X, [_|L]) :– !, element3(X, L).
şi interogările:
Goal: element1(E, [a,b,c,d])
Goal: element2(E, [a,b,c,d])
Goal: element3(E, [a,b,c,d])
Care vor fi răspunsurile dacă scopurile sunt nondeterministe? Să se comenteze
răspunsurile.

41
2. Să se explice de ce în predicatul intersectie e necesar să fie inclus predicatul
"!".
3. Admitem ca listele L1 şi L2 sunt mulţimi. Să se definească predicatul
reuniune(L1,L2,L3), unde L3=L1L2 şi predicatul diferenta(L1,L2,L3), unde
L3=L1\L2.
4. Să se definească un predicat substitutie(X,Y,L1,L2), unde lista L2 este lista L1
după ce toate elementele X din L1 s-au substituit cu Y.
5. Considerăm o listă de forma: [prof(mihai,inginer),prof(ion,contabil),…],
unde elementele listei au forma prof(nume,profesie). Să se scrie un program
care sortează elementele listei în ordine crescătoare a valorilor atributului
nume.

42
6. Ferestre în Turbo Prolog
Turbo Prolog permite definirea şi utilizarea unor ferestre pe ecranul
calculatorului. Unele ferestre pot să se reacopere. Ferestrele pot fi utilizate şi în
calitate de meniuri. O fereastră e definită prin poziţia ei pe ecran şi culorile ei.
Codurile de culoare pentru display color se stabilesc cu ajutorului tabelului 1.
Culoare Fond (ecran) Scris (caractere)
negru 0 0
albastru 16 1
verde 32 2
cyan (bleu) 48 3
roşu 64 4
magenta (violet) 80 5
maro 96 6
alb 112 7
gri 8
albastru-deschis 9
verde-deschis 10
cyan-deschis 11
roşu-deschis 12
magenta-deschis 13
galben 14
alb-intens 15
Tabelul 1. Atributele de culoare pentru display color.
Predicatul makewindow defineşte o fereastră şi are forma generală:
makewindow(Nrf, ScrAtr, Chenar, Titlul, Y, X, H, L)
Prototipurile predicatului pot fi (i,i,i,i,i,i,i,i) sau (o,o,o,o,o,o,o,o).
Argumentul Titlul are tipul string. Celelalte argumente sunt de tipul integer.
Argumentele au semnificaţia:
- Nrf este numărul ataşat ferestrei (1..127);
- ScrAtr stabileşte atributele video ale ferestrei şi reprezintă suma
fond+scris din tabelul 1;
- chenar stabileşte atributele video ale chenarului şi ale titlului şi
reprezintă suma fond+scris din tabelul 1;
- Titlul defineşte un text care se afişează pe marginea superioară a
ferestrei;
- Y, X stabileşte coordonatele colţului din stânga-sus pentru fereastră
(numărul liniei şi, respectiv, numărul coloanei);

43
- H este înălţimea ferestrei;
- L este lăţimea ferestrei.
Alte predicate care pot fi utilizate sunt următoarele:
 shiftwindow(Nrf)
Prototipuri: (i), (o).
Pentru prototipul (i) predicatul activează fereastra cu numărul Nrf şi o
trece în prim-plan. Dacă prototipul este (o), atunci Nrf se concretizează cu
numărul ferestrei curente.
 gotowindow(Nrf)
Prototipuri: (i).
Predicatul activează fereastra cu numărul Nrf.
 removewindow
Predicatul distruge fereastra curentă.
 clearwindow
Predicatul curăţă fereastra curentă şi instalează cursorul în colţul stânga-
sus.
 cursor(Y, X)
Prototipuri: (i,i), (o,o).
Dacă prototipul este (i,i), atunci predicatul poziţionează cursorul pe linia
Y şi coloana X. Dacă prototipul este (o,o), atunci Y şi X sunt legate de
coordonatele cursorului.
La fiecare revenire în fereastră se continuă scrierea din poziţia în care a
rămas cursorul în momentul trecerii la altă fereastră. Pentru pulsarea imaginii la
atributul culoare se adaugă numărul 128.
Exemplu. Urmăriţi execuţia următoarei clauze:
Goal: makewindow(3,110,64,"F3",5,20,10,25),
makewindow(4,20,100,"F4",2,10,5,30),
write("Sunt in F4"), readchar( _ ),
shiftwindow(3),
write("Sunt in F3"), readchar( _ ),
gotowindow(4)
write("Sunt in F4"), readchar( _ ).

44
7. Predicate predefinite
7.1. Predicate de lucru cu caractere şi string-uri
Prezentăm câteva predicate standard pentru prelucrarea obiectelor de tip
string, precum şi predicate de conversie. La descrierea acestor predicate prima
linie reprezintă formatul predicatelor. Denumirile argumentelor specifică tipurile
lor. Pe linia a doua vor fi specificate domeniile argumentelor şi prototipurile lor.
Prin i specificăm un argument de intrare, iar prin o - de ieşire (i – input, o –
output). Argumentele cu prototipul i pot fi constante sau variabile legate.
- frontchar(String,FrontChar,RestString)
(string,char,string) - (i,o,o) (i,i,o) (i,o,i) (i,i,i) (o,i,i)
Primul caracter din String este Frontchar.
Exemple.
1. Prototipul (i,o,o).
Goal: frontchar("dan", X,Y)
răspuns: X="d", Y="an"
2. Prototipul (o,i,o).
Goal: frontchar("dan",X,"an")
răspuns: X="d"

- fronttoken(String,Token,RestString)
(string,string,string) - (i,o,o) (i,i,o) (i,o,i) (i,i,i) (o,i,i)
Un Token al unui String este primul cuvânt al String-ului.
Exemple.
1. Prototipul (i,o,o).
Goal: fronttoken("Baze de date", X,Y)
răspuns: X="Baze" , Y="de date"
2. Prototipul (o,i,i).
Goal: fronttoken(X,"Baze","de date")
răspuns: X="Baze de date"

45
- frontstr(Lenght,Inpstring,StartString,RestString)
(integer,string,string,string) - (i,i,o,o)
StartString se concretizează cu primele Length caractere din Inpstring.
Exemplu.
Goal: frontstr(3,"anamaria",X,Y)
răspuns: X="ana", Y="maria"

- concat(String1,String2,String3)
(string,string,string) - (i,i,o) (i,o,i) (o,i,i) (i,i,i)
String3 = String1 + String2

Exemple.
1. Prototipul (i,i,o).
Goal: concat("non", "determinism",Y)
răspuns: Y="nondeterminism"
2. Prototipul (i,i,i).
Goal: concat("non","determinism","nondeterminism")
răspuns: Yes

- str_len(String,Length)
(string,integer) - (i,i) (i,o)
Determină lungimea lui String.
Exemplu.
1. Prototipul (i,o).
Goal: str_len("Prolog",X)
răspuns: X=6

- isname(StringParam)
(string) - (i)
este adevărat dacă argumentul este un identificator.

46
isname("bani") – adevăr,
isname("2bani") – fals.

Predicate de conversie
- char_int(CharParam,IntgParam)
(char,integer) - (i,o) (o,i) (i,i)
Exemple.
1. Prototipul (i,o).
Goal: char_int('A',X)
răspuns: X=65 % codul ASCII al caracterului 'A'
2. Prototipul (o,i).
Goal: char_int (X,66)
răspuns: X='B'

- str_int(StringParam,IntgParam)
(string,integer) - (i,o) (o,i) (i,i)
Transformă un string într-un integer sau invers.
Exemplu.
Goal: str_int("235",X)
răspuns: X=235
Goal: str_int(X,125)
răspuns: X="125"

- str_char(StringParam,CharParam)
(string,char) - (i,o) (o,i) (i,i)
Transformă un string de un singur caracter într-un caracter (char).

- str_real(StringParam,RealParam)
(string,real) - (i,o) (o,i) (i,i)
Transformă un string într-un real sau invers.
47
Exemplu.
Goal: str_real("3.14",X)
răspuns: X=3.14

- upper_lower(StringInUpperCase,StringInLowerCase)
(string,string) - (i,i) (i,o) (o,i)
Transformă literele mici dintr-un string în mari şi invers.

- upper_lower(CharInUpperCase,CharInLowerCase)
(char,char) - (i,i) (i,o) (o,i)
Transformă o literă mare în mică şi invers.

Exerciţiu. Considerăm o listă de string-uri. Unele elemente ale listei


conţin cifre. Să se definească un predicat:
cifra_str(Cifra, L_in, L_out),
care selectează în lista L_out elementele din lista L_in pentru care Cifra este
prima cifră care se întâlneşte în string.
Pentru Goal: cifra_str(4,
["ab2c","MI43","24ci","abc","4CD","a41q"],X)
se va răspunde: X=["MI43","4CD","a41q"]

7.2. Predicate de lucru cu fişiere


Un fişier are un nume DOS sub care este recunoscut de sistemul de
operare şi un nume simbolic. Numele simbolic se declară în secţia domains în
felul următor:
file=nume_simbolic;...
Nume simbolice standarde ce nu trebuie declarate sunt:
keyboard - citire de la tastatură
screen - ieşire la ecran
printer - ieşire la imprimantă

48
Numele standard poate fi înlocuit cu alt nume. Astfel poate fi dirijată
intrarea/ieşirea informaţiilor. În acest scop se utilizează predicatele:
readdevice(Nume_simbolic)
writedevice(Nume_simbolic)
Predicatele pentru deschiderea şi închiderea unui fişier:
openwrite(Nume_simbolic, “Nume_DOS”)
openread(Nume_simbolic, “Nume_DOS”)
closefile(Nume_simbolic)
Alte predicate:
existfile(“Nume_DOS”) - verifică existenţa fişierului
disk(“Cale”) – stabileşte calea spre fişier
system(“Comanda”) – execută o comandă DOS. Se revine la
mediul Prolog.

7.3. Predicatele bound şi free


Predicatul bound are forma:
bound(Variabilă)
Dacă variabila este legată (concretizată) atunci bound(Variabilă) întoarce
valoarea adevăr şi fals în caz contrar.
Predicatul free are forma:
free(Variabilă)
Dacă variabila este liberă, atunci free(Variabilă) întoarce valoarea
adevăr şi fals în caz contrar.

Exemplu. Admitem C temperatura în oC şi F – temperatura în F


(Fahrenheit). Temperatura se trece dintr-o scară în alta în felul următor:
F=(C*9/5+32) sau C=5/9*(F-32)
Predicatul t(C,F) ne permite să calculăm temperatura în Co dacă
cunoaştem F şi invers:
t(C,F):– free(C), bound(F),

49
C=5/9*(F-32), !.
t(C,F):– bound(C), free(F),
F=C*1.8+32.
La interogarea Goal: t(10, F)
se răspunde: F=50
iar la interogarea Goal: t(C,50)
se răspunde C=10

7.4. Predicatul include


Un program Prolog poate fi conceput modular. Modulele pot fi incluse în
programul de bază, utilizând predicatul include care are următoarea formă
sintactică:
include(“Nume_DOS”)
unde Nume_DOS are extensia ".pro" şi este un fişier-text. Acest predicat se
scrie înaintea programului (înainte de secţiunea domains) şi se include în timpul
compilării. Domeniile şi predicatele trebuie să fie compatibile. În acest caz se
vor utiliza domenii şi predicate globale.

7.5. Predicatul exit


Acest predicat întrerupe executarea programului şi transmite dirijarea
sistemului Turbo Prolog.

Predicatele trace şi shorttrace.


În procesul trasării predicatul trace, spre deosebire de predicatul
shorttrace, nu face optimizarea internă. Trasarea poate fi iniţiată prin meniul
principal (Options-Compiler directives), iar pentru a urmări pas cu pas
executarea programului, se utilizează tasta F10. Aceste predicate pot fi incluse şi
în textul programului. În acest caz la începutul programului se scrie unul dintre
predicatele:
trace

50
trace p1,p2,...pn
shorttrace
shorttrace p1,p2,...pn
unde p1, p2, ..., pn sunt nume de predicate care se vor trasa. Trace sau shorttrace
fără argumente trasează toate predicatele din program.

7.6. Predicatul code


Acest predicat se utilizează pentru a mări spaţiul codului generat şi are
forma:
code = n
unde n este numărul de paragrafe. Pentru codul nemodificat n=1000.
Acest predicat se scrie la începutul programului (prima linie).

7.7. Predicatele date şi time


Predicatul date are forma:
date(Anul, Luna, Ziua)
prototipul: (i,i,i), (o,o,o)
Argumentele sunt de tip integer. Prototipul (o,o,o) indică data curentă, iar
prototipul (i,i,i) modifică data.
Predicatul time are următoarea formă sintactică:
time(Ora, Min, Sec, Sutimi)
prototipul: (i,i,i,i), (o,o,o,o)
Acest predicat indică sau modifică ora.

7.8. Predicatele beep şi sound


Predicatul beep emite un sunet. Predicatul sound are următorul format:
sound(Durata, Frecvenţa)
prototipul: (integer,integer) – (i,i)
emite un sunet de durata şi frecvenţa dată.

51
8. Baze de date interne (dinamice)
Un program Prolog, de regulă, conţine fapte şi reguli. Numărul de fapte din
program poate fi destul de mare şi deseori se modifică. De exemplu, dacă avem o
problemă de căutare în graf şi dacă graful se defineşte cu ajutorul faptelor, atunci
numărul de fapte poate fi destul de mare. Regulile de procesare se pot aplica la
diferite grafuri, deci faptele se pot modifica. E de dorit ca descrierea grafurilor să
se efectueze în afara programului de bază. Altfel-zis, faptele să se insereze într-o
bază de date (de cunoştinţe). Această posibilitate ne este oferită de bazele de date
interne (BD interne).
Prolog ne oferă posibilitatea de a lucra cu baze de date în memoria RAM.
În Turbo Prolog în baza de date internă se pot insera numai fapte. Baza de date
poate fi salvată pe disc, cu scopul de a fi reutilizată.
Programele Prolog care utilizează BD interne au structura:
domains
..........
database
..........
predicates
..........
clauses
..........
goal

În secţiunea database se declară predicatele ce se conţin în BD internă.


Predicatele din BD diferă de cele din secţiunea predicates. Într-un program pot fi
incluse mai multe BD. Atunci programul va conţine mai multe secţiuni database
care au forma:
database - nume_bd
<predicate>
Dacă faptele unei BD interne sunt înscrise în memorie, aceasta
echivalează cu înscrierea lor în program.
Faptele în BD internă sunt aranjate (grupate) în dependenţă de numele
predicatelor.

52
Predicatele standard pentru lucrul cu BD interne sunt:
1. asserta(nume_predicat(arg1, …,argn) [,nume_bd])
Predicatul asertează (adaugă) un fapt la baza de date internă. Faptul va fi
primul din grupul de predicate existent.
2. assertz(nume_predicat(arg1, …,argn) [,nume_bd])
sau
assert(nume_predicat(arg1, …,argn) [,nume_bd])
Faptul va fi asertat la baza de date internă. El va fi ultimul din grupul de
predicate existent. Argumentele nu pot fi variabile neconcretizate.
3. retract(nume_predicat(arg1, …,argn) [,nume_bd])
Aici argumentele pot fi atât constante, cât şi variabile. Predicatul elimină
un fapt din baza de date. Faptul ce se elimină este primul cu care se reuşeşte
unificarea. Predicatul este util şi pentru a extrage date din bază (eliminându-l în
acelaşi timp din bază).
4. retractall(nume_predicat(arg1, …,argn) [,nume_bd])
Predicatul elimină din baza de date internă toate faptele cu care se
reuşeşte unificarea.
5. save(“Nume_fisier_DOS” [,nume_bd])
Predicatul salvează faptele din bază într-un fişier DOS.
Exemplu. Goal: save(“A:\stud.dat”)
6. consult(“Nume_fisier_DOS” [,nume_bd])
La baza de date internă se adaugă faptele din fişierul
“Nume_fisier_DOS”.
Exemple.
1. Considerăm programul
database
suma(integer)
predicates
initiere
inc
clauses
initiere:–retract(suma(_)),assert(suma(0)),!.
initiere:–asserta(suma(0)).

53
inc:–retract(suma(X)),Y=X+1,assert(suma(Y)).
La întrebarea Goal: initiere, suma(X)
se răspunde: X=0
1 Solution
La întrebarea Goal: inc
se răspunde: YES
Iar la Goal: suma(X)
se răspunde: X=1
2. Considerăm programul:
domains
nume, functia = symbol
sectia = integer
salariu = real
database
suma(salariu)
salariat(nume, functia, sectia, salariu)
predicates
adauga
init
suma_sal
sum1(sectia)
set_sum(salariu)
clauses
adauga:–write(“Continuati?(y/n)”),
readchar(X),X=’y’,
write(“Numele?”), readln(Num),
write(“Functia?”), readln(F),
write(“Sectia?”), readint(N),
write(“Salariul?”),readreal(Sal),
assert(salariat(Num,F,N,Sal)),
adauga.
adauga.
suma_sal:–init,write(“Sectia?”),
readint(X),sum1(X).
sum1(S):–salariat(_,_,S,Salariu),
set_sum(Salariu),fail.
sum1(_):–suma(X),write(X),nl.
set_sum(X):–retract(suma(Y)),T=Y+X,
assert(suma(T)),!.
init:–retract(suma(_)),assert(suma(0)),!.
init :– assert(suma(0)).
54
Scopul Goal: adauga
adaugă fapte salariat(…) în baza de date interne.
Scopul Goal: suma_sal
calculează suma salariului pentru salariaţii din secţia indicată.
În regula adauga, pentru a organiza ciclul, n-am utilizat fail, întrucât
regula nu conţine subscopuri care ar genera mai multe soluţii. În astfel de cazuri
fail poate fi utilizat împreună cu predicatul repeat.
Suma salariului poate fi calculată utilizând predicatul findall. Predicatul
sum1(S) poate fi scris astfel:
sum1(S):–findall(X,salariat(_,_,S,X),L),
sum_list(L,Suma).
sum_list([],0).
sum_list([X|L1],R):–sum_list(L1,R1),R=R1+X.
Dacă baza de date internă conţine faptele
salariat(ion,contabil,10,900).
salariat(ana,programator,12,800).
salariat(dan,inginer,10,750).
atunci pentru S=10, L din sum1 va fi: [900,750].
Exerciţiu. Presupunem că baza de date conţine fapte de forma:
parinte(P,C).
unde P este părintele lui C.
Să se scrie un program care:
- adaugă fapte în baza dinamică;
- afişează toţi strămoşii lui C şi lista fraţilor/surorilor strămoşilor lui C.
De exemplu, dacă C=andrei şi ion este părinte, suzana este bunica, atunci se
afişează:
ion [ada,dan]
suzana [ana,maria,ala]
...............................
Deci, în baza de date se conţin faptele:
parinte(ion,andrei).
parinte(suzana,ion).

55
parinte(suzana,ada).
parinte(suzana,dan).
parinte(adrian,suzana).
parinte(adrian,ana).
parinte(adrian,maria).
parinte(adrian,ala).
.........................
În liste fiecare element apare o singură dată.

56
9. Prolog şi baze de date relaţionale
Modelul relaţional al datelor poate fi eficient reprezentat în Prolog. Bazele
de date interne şi cele externe (capitolul 12) permit elaborarea bazelor de date
care satisfac toate cerinţele actuale.

9.1. Reprezentarea relaţiilor


Relaţiile pot fi reprezentate ca o colecţie de fapte în baza de date
dinamică sau ca lanţuri în bazele de date externe.
Vom examina modelul relaţional în cadrul bazelor de date dinamice.
Considerăm relaţia angajat cu schema:
angajat(nume, post, departament, data_angajării, salariu)
Dan Manager 200 10.02.2002 3000
Ana Secretar 151 19.10.2003 2000
Petru Inginer 200 03.03.2002 2500
….... …….. ……. ……….. …….

Această relaţie în Prolog poate fi reprezentată în formă de fapte:


angajat(dan,manager,200,”10.02.2002”,3000).
angajat(ana,secretar,151,”19.10.2003”,2000).
angajat(petru,inginer,200,”03.03.2002”,2500).

unde fiecare tuplu este reprezentat printr-un fapt. Considerăm că în relaţia


angajat cheia este atributul nume. Pot fi utilizate şi alte metode de reprezentare
în formă de fapte. De exemplu, pentru Dan atributele pot fi reprezentate astfel:
departament(dan,200).
post(dan,manager).
data_ang(dan,”10.02.2002”).
salariu(dan,3000).
Dacă trebuie să manipulăm cu tupluri, atunci aceste atribute le putem uni cu
regula:
angajat1(Num,Post,Dep,Dat,Sal):-post(Num,Post),
departament(Num,Dep),
data_ang(Num,Data),
salariu(Num,Sal).

57
În primul caz interogarea bazei de date are forma:
Goal: angajat(N,P,D,Data,S)
iar în al doilea caz are forma
Goal: angajat1(N,P,D,Data,S)
În ambele cazuri se va răspunde la fel.
Reprezentarea atributelor în formă de fapte este un procedeu mai flexibil
decât reprezentarea sub formă de tupluri, deoarece mai uşor putem modifica
schema relaţiei. În acest caz modificarea schemei implică numai modificarea
regulii angajat1, dar nu şi a datelor din baza de date. De exemplu, putem adăuga
fapte de forma:
data_eliberarii(dan,”07.09.2003”).
În regula angajat1 este suficient să adăugăm un singur argument.
Relaţiile bazei de date pot fi reprezentate în Prolog şi sub formă de listă
de fapte (tupluri). Pentru relaţia angajat avem lista:
[angajat(dan,manager,200,”10.02.2003”,3000),
angajat(ana,secretar,151,”19.10.2002”,2000),
angajat(petru,inginer,200,”03.03.2002”,2500)]

În acest caz elementele relaţiei pot fi tratate ca elementele unei liste.


Considerăm predicatul tipar:
tipar([]).
tipar([X|L]):-X=angajat(Num,_._,_,S),
write(Num," ",S),nl,tipar(L).

Dacă argumentul predicatului tipar corespunde domeniului angajat(...), atunci


acest predicat afişează numele angajaţilor şi a salariului respectiv. Observăm că
se afişează proiecţia relaţiei angajat după atributele Nume şi Salariu.

58
9.2. Algebra relaţională
Operaţiile algebrei relaţionale se realizează eficient în Prolog. Considerăm
relaţiile r(A,B,C), q(A,B,C) şi s(D,E). Atunci reuniunea poate fi definită astfel:
r_union_q(A,B,C):-r(A,B,C).
r_union_q(A,B,C):-q(A,B,C).
Pentru un goal nondeterminist
Goal: r_union_q(X,Y,Z)
răspunsul va fi relaţia r  q.
Diferenţa r \ q poate fi definită astfel:
r_diff_q(A,B,C):-r(A,B,C), not(q(A,B,C)).
Intersecţia a două relaţii de aceeaşi aritate poate fi definită utilizând diferenţa
sau regula:
r_inter_q(A,B,C):-r(A,B,C), q(A,B,C).
Produsul cartezian poate fi definit cu o singură regulă:
r_multi_s(A,B,C,D,E):-r(A,B,C), s(D,E).
Dacă r are aritatea m, iar s aritatea n, atunci produsul are aritatea m+n.
Operaţia de selectare am utilizat-o în mai multe exemple.
Cu o singură regulă poate fi definită şi joncţiunea equijoin:
r_join_s(A,B,C,E):-r(A,B,C), s(D,E), D=C.
Deci, operaţiile algebrei relaţionale se exprimă nemijlocit prin construcţiile
limbajului Prolog.

9.3. Interogarea bazelor de date


Prolog permite utilizarea listelor şi a recursiei în procesul de manipulare a
datelor. Să examinăm o bază de date "Furnizor" cu relaţiile:
fr, mf şi fm,
unde fr – furnizor, mf – marfă, fm – furnizor_marfă.
Schemele acestor relaţii sunt:

fr(CodF, Nume, Oras, Telefon)

59
mf(CodM, Denumire, Greutate, Oras)
fm(CodF, CodM, Cantitate)
unde CodF – codul furnizorului, CodM – codul mărfii. Toate atributele sunt de
tip string. Atributele CodM şi CodF aparţin cheilor.
Presupunem că în baza de date internă sunt inserate faptele:
fr("F1","Sandu","Chisinau","222222").
fr("F2","Dan","Orhei","32277").
fr("F3","Calin","Balti","337111").
fr("F4","Ion","Iasi","442223").
fr("F5","Andrei"," Chisinau ","333333").

mf("M1","Piulita","12"," Orhei ").


mf("M2","Surup","17"," Chisinau ").
mf("M3","Elice","37","Cahul").
mf("M4","Cama","19"," Balti ").

fm("F1","M1","300").
fm("F1","M2","200").
fm("F1","M3","250").
fm("F1","M4","100").
fm("F2","M2","200").
fm("F2","M3","150").
fm("F2","M1","100").
fm("F3","M2","250").
fm("F4","M4","100").
fm("F4","M3","150").

Problema 1. Să se afişeze numele furnizorilor care au livrat toate tipurile


de marfă.
Această interogare în limbajul SQL [3] poate fi scrisă astfel:
SELECT Nume
FROM fr
WHERE NOT EXISTS
(SELECT *
FROM mf
WHERE NOT EXISTS
(SELECT *

60
FROM fm
WHERE CodF=fr.CodF
AND CodM=mf.CodM));
Pentru a scrie în Prolog această interogare, vom utiliza predicatul standard
findall şi predicatul sublist definit anterior.
Predicatul l_marfa înscrie codurile tuturor mărfurilor în L. Predicatul
select_fr selectează câte un furnizor pentru ca predicatul prelucrare să
stabilească dacă furnizorul dat a livrat toate mărfurile din lista L. Pentru aceasta
predicatul prelucrare înscrie în lista L1 toate mărfurile livrate de către furnizor
şi verifică dacă L  L1.
l_marfa:-findall(CodM,mf(CodM,_,_,_),L),select_fr(L).

select_fr(L):-fr(CodF,_,_,_),prelucrare(CodF,L),fail.
select_fr(_):-readchar(_).

prelucrare(CodF,L):-findall(CodM,fm(CodF,CodM,_),L1),
sublist(L,L1),fr(CodF,Nume,_,_),
nl,write(Nume),!.
prelucrare(_,_):-!.

Pentru Goal: l_marfa


răspunsul va fi acelaşi ca şi în SQL:
Sandu

Problema 2. Să se afişeze numele furnizorilor care au livrat marfa M2.


Predicatul fr_mf rezolvă această problemă.
fr_mf(M):-fm(X,M,_),fr(X,Num,_,_),
nl,write(Num),fail.
fr_mf(_):-nl,readchar(_).
La interogarea
Goal: fr_mf("M2")
răspunsul va fi
Sandu
Dan

61
Calin
În SQL această interogare poate fi scrisă astfel:
SELECT Nume
FROM fr
WHERE 'M2' IN
(SELECT CodM
FROM fm
WHERE CodF=fr.CodF);
Exerciţii.
1. Selectaţi şi afişaţi codurile furnizorilor care nu au livrat marfă.
2. Selectaţi şi afişaţi codurile furnizorilor care au livrat cel puţin o marfă
ca şi furnizorul "F2".
3. Selectaţi şi afişaţi codurile furnizorilor şi codurile mărfurilor pentru
care atributul Oras are aceeaşi valoare.
4. Calculaţi şi afişaţi greutatea totală a mărfurilor livrate de către
furnizorul "F2".

62
10. Programarea nondeterministă
Nondeterminism semnifică lipsa instrucţiunilor (faptelor) care ar
determina căile de căutare a soluţiilor. Un program este nondeterminist dacă
admite mai multe evaluări (calcule). Altfel-zis, arborele de realizare al
programului conţine ramificări. Ramificările pot fi determinate de către goal
care conţine mai multe subscopuri, precum şi de către existenţa a mai multor
clauze care pot satisface scopul.
Spunem că o maşină este nondeterministă dacă în cazul când apar mai
multe căi de soluţionare a problemei maşina selectează calea corectă. O maşină
cu adevărat nondeterministă nu se poate realiza, însă o putem modela cu un
anumit grad de aproximaţie.
Nondeterminismul este un principiu eficient de programare şi se aplică cu
succes în programarea logică.

10.1. Metoda "generează-testează"


Existenţa mai multor clauze care pot satisface scopul determină (implică)
ramificările arborelui de realizare a programului. În acest caz avem
nondeterminism cu un număr necunoscut de alternative. Considerăm un arbore
arbitrar (fig.6).

b d
c

e f g h

Fig.6. Arbore arbitrar.


Acest graf poate fi definit în Prolog astfel:
arc_(a,b). arc_(a,c). arc_(a,d).
arc_(c,e). arc_(e,i). arc_(c,f).

63
arc_(d,g). arc_(d,h).
Predicatul drum(X,Y,D) este adevărat dacă există drum de la nodul X la nodul
Y.
drum(X,X,[X]).
drum(X,Y,[X|P]):- arc_(X,Z),drum(Z,Y,P).
Pentru a satisface scopul drum(a,g,D) sistemul examinează nodurile b, c, e, i, f,
d, g, întrucât în Prolog soluţiile se caută după principiul "căutare în profunzime",
însă D=[a, d, g]. Deci au fost generate mai multe soluţii posibile. Observăm că
dacă ar lipsi faptele care conţin nodurile b, c, e, f şi i, atunci pentru scopul
drum(a,g,D) ar fi mai puţine evaluări.
Considerăm graful din fig.7.
c

a b d

e
Fig.7. Graf orientat.
Dacă căutăm un drum de la a la d, se vor genera mai multe soluţii. În
astfel de cazuri, de regulă, se verifică şi se alege soluţia cea mai bună. De
exemplu, se selectează soluţia pentru care lista să conţină mai puţine elemente.
În acest caz se aplică metoda "generare şi testare". Esenţa acestei metode constă
în faptul că un proces generează una sau mai multe soluţii, iar al doilea proces
verifică aceste soluţii posibile.
Această metodă se utilizează des la proiectarea algoritmilor şi a
programelor. În Prolog metoda "generare şi testare" are forma:
soluţie(X):– generare(X), testare(X).
şi reprezintă un exemplu de programare nondeterministă. Pentru a genera toate
soluţiile posibile în Prolog, de regulă, utilizăm predicatul fail. Pentru a face ca
un program să fie determinist, putem utiliza predicatul !(cut).

64
În programarea logică nu ne interesează modul de organizare a ciclului
pentru generarea soluţiilor posibile. Soluţii posibile pot fi obiecte simple sau
compuse. De asemenea, pot fi şi elementele unei liste. În acest caz soluţiile sunt
generate de predicatul member(X,L), unde L este listă şi poate să conţină
elemente compuse. De exemplu, pentru scopul member(X,[a,b,c]) obţinem
X=a, X=b, X=c.
Predicatul member( ) poate fi definit şi cu ajutorul predicatului append:
member(X,L):– append(_,[X|_],L).
În această regulă se utilizează metoda "generare şi testare". Aici generarea şi
testarea se contopesc în procesul unificării.
Un alt exemplu de generare şi testare poate fi regula:
intersectie(L1,L2):– member(X,L1),member(X,L2).
Primul subscop member(X,L1) generează elemente din L1, iar al doilea subscop
member(X,L2) verifică dacă elementul aparţine listei L2.

Exemple de utilizare a metodei "generare şi testare".


Exemplul 1. Selectarea recruţilor (bărbaţilor de 18 ani).
Admitem secvenţa de program:
persoana(ana,20).
persoana(ion,18).
persoana(dan,17).
persoana(stefan,18).
barbat(ion).
barbat(stefan).
barbat(dan).
femeie(ana).
recrut:-persoana(X,Y),verifica(X,Y).
verfica(X,Y):-barbat(X),Y=18,nl,write(X).

La interogarea Goal: recrut


răspunsul este ion
Pentru a obţine lista tuturor recruţilor, putem utiliza fail. Corpul regulii
recrut conţine conjuncţia a două subscopuri: persoana(X,Y) care generează
variante noi şi verifica(X,Y) care testează variantele propuse.

65
10.2. Problema colorării unei hărţi
Graful de colorare al unei hărţi este reprezentat prin mulţimea de noduri
şi mulţimea de muchii. Fiecare muchie e redată prin perechea de noduri pe care
le leagă. Graful poate fi următorul termen structurat:
g( [Nod1,Nod2,…], [m(Nod1,Nod2),…] )
unde primul argument este lista nodurilor, iar al doilea argument este lista
muchiilor. Predicatul
colorare(Graf, Culori, Colorare)
are trei argumente. Graf şi Culori sunt de intrare, şi Colorare – de ieşire.
Argumentul Colorare este o listă de perechi nod-culoare. Programul lucrează în
felul următor: se generează o atribuire totală (fiecărui nod i se atribuie câte o
culoare) cu ajutorul predicatului generare şi apoi predicatul testare verifică dacă
această atribuire este validă.
domains
nod=symbol
culori=symbol*
nodculoare=symbol*
listanod =nod*
muchie=m(nod,nod)
listamuchii=muchie*
graf=g(listanod,listamuchii)
predicates
colorare(graf,culori,nodculoare)
generare(listanod,culori,nodculoare)
testare(listamuchii,nodculoare)
apartine(symbol,culori)
apartine1(symbol,symbol,nodculoare)
egal(symbol,symbol)
clauses
colorare(g(LN,LM),Culori,Colorare):-
generare(LN,Culori,Colorare),
testare(LM,Colorare).
generare([],_,[]).
generare([N|LN],Culori,[N,C|T]):-
% generarea nondeterminista a culorilor
apartine(C,Culori),generare(LN,Culori,T).

testare([],_).
testare([m(N1,N2)|LM],Colorare):-
66
%gasirea culorii nodului N1
apartine1(N1,C1,Colorare),
%gasirea culorii nodului N2
apartine1(N2,C2,Colorare),
%testarea diferentei de culori
not(egal(C1,C2)),testare(LM,Colorare).
%determinarea apartenentei unui element listei
apartine(X,[X|_]).
apartine(X,[_|Y]):-apartine(X,Y).
%determinarea apartenentei a doua elemente unei liste
apartine1(X,Y,[X,Y|_]).
apartine1(X,Y,[_,_|Z]):-apartine1(X,Y,Z).
%determinarea de egalitate a doua elemente
egal(X,Y):-X=Y.
La un goal exterior:
Goal:
colorare( g( [a,b,c,d],
[m(a,b),m(a,c),m(d,c),m(a,d),m(b,c)] ) ,
[alb,rosu,verde] , F )
Se va răspunde:
F=["a","alb","b","rosu","c","verde","d","rosu"]
F=["a","alb","b","verde","c","rosu","d","verde"]
F=["a","rosu","b","alb","c","verde","d","alb"]
F=["a","rosu","b","verde","c","alb","d","verde"]
F=["a","verde","b","alb","c","rosu","d","alb"]
F=["a","verde","b","rosu","c","alb","d","rosu"]
6 Solutions

Exerciţii.
1. Admitem programul:
cifra(0). cifra(1). cifra(2).

numar(X,Y):-cifra(X),cifra(Y),X<>Y,write(X,Y),nl.
numar(X,Y):-cifra(X),cifra(Y),X=Y,write(X,Y),nl.
şi un scop nondeterminist Goal numar(A,B). Explicaţi cum se obţin soluţiile.
2. Prelucrarea mulţimilor. Fie predicatul:
intersectie([],_,[]).
intersectie([X|R],Y,[X|Z]):-member(X,Y),!,
intersectie(R,Y,Z).
intersectie([_|R],Y,Z):-intersectie(R,Y,Z).
şi un scop nondeterminist. Explicaţi cum se obţin soluţiile (corecte şi incorecte)
în cazul când este exclus predicatul !(cut).

67
11. Aplicaţii Prolog
11.1. Legi de compoziţie
Să considerăm mulţimea M={a,b,c} şi aplicaţia T definită astfel:
T a b c
a b c a
b c a b
c a b c

Această aplicaţie în Prolog poate fi reprezentată cu ajutorul faptelor de


forma t(x,y,z), unde x, y, z  M.
Programul ”Semigrup” (fig.8) verifică dacă aplicaţia este o lege de
compoziţie şi dacă legea este asociativă.
% Semigrup
domains
s=symbol ls=s*
predicates
m(ls) t(s,s,s)
compozitie(s,s) aplicatie
asociativ egal(s,s,s,s,s)
element(s,ls)
del_el(s,ls,ls)
start
clauses
m([a,b,c]).
t(a,a,b). t(a,b,c). t(a,c,a).
t(b,a,c). t(b,b,a). t(b,c,b).
t(c,a,a). t(c,b,b). t(c,c,c).
aplicatie:-m(M),element(A,M),element(B,M),
compozitie(A,B),fail.
aplicatie.

compozitie(A,B):-m(M),t(A,B,Y),del_el(Y,M,M1),
not(element(Y,M1)),!.
compozitie(A,B):-
writef("Eroare.Compozitia:%2%2",A,B),
nl.

asociativ:-m(M),
68
element(X,M),element(Y,M),element(Z,M),
t(X,Y,R1),t(R1,Z,R2),
t(Y,Z,R3),t(X,R3,R4),
egal(X,Y,Z,R2,R4),fail.
asociativ.

egal(_,_,_,R2,R4):- R2=R4,!.
egal(A,B,C,_,_):-
writef("Eroare.Asociativitate?%2%2%2",A,B,C),nl.

element(X,[X|_]).
element(X,[_|L]):-element(X,L).
del_el(X,[X|L],L):-!.
del_el(X,[Y|L],[Y|L1]):-X<>Y,del_el(X,L,L1).

start:-write("Inceput."),nl,aplicatie,asociativ,
write("Sfirsit.").

goal start.
Fig.8. Programul ”Semigrup”.

11.2. Grafuri
Mai multe tipuri de probleme se reduc la o problemă din teoria grafurilor.
Să considerăm un labirint (fig.9 a) care e format din mai multe camere şi care
sunt “unite” (legate) prin uşi. Dacă există uşă între camerele x şi y atunci se
poate trece din camera x în y şi invers. Deci, relaţia “trecere” are proprietatea
xRy  yRx.

f d c d c

f
g

g e b
i h e b
i h a
a

a) b)
Fig. 9. Reprezentarea unui labirint cu ajutorul unui graf.

69
Labirintul poate fi reprezentat şi cu ajutorul unui graf (fig.9 b).
Presupunem că fiecare cameră este notată printr-o literă. Dacă omul ar căuta
trecerea, s-ar strădui să însemne sau să memorizeze camerele vizitate. Altfel se
poate "rătăci". Programul Prolog procedează analogic. Camerele “vizitate” sunt
inserate într-o listă cu scopul de a evita ciclul veşnic.
/*** Labirint ***/
domains
s=symbol ls=s*
predicates
apartine(s,ls) u(s,s)
1 trecere(s,s,ls) trecere1(s,s,ls,ls)
clauses
u(a,b). u(b,c). u(b,e).
u(e,d). u(d,c). u(d,f).
u(h,i). u(h,g).

trecere(X,Y,R):-trecere1(X,Y,[],R),L=[X|R],
write(L),nl,fail.
trecere(_,_,_).

trecere1(X,X,_,[]).
trecere1(X,Y,T,L):-u(X,Z),not(apartine(Z,T)),
trecere1(Z,Y,[Z|T],L1),L=[Z|L1].
trecere1(X,Y,T,L):-u(Z,X),not(apartine(Z,T)),
trecere1(Z,Y,[Z|T],L1),L=[Z|L1].
apartine(X,[X|_]).
apartine(X,[_|L]):-apartine(X,L).
goal clearwindow,trecere(a,f,L).
Fig.10 Programul “Labirint”.

Programul “Labirint” determină dacă există o trecere dintr-o cameră, de


exemplu, din a în una din camerele b, c,… În acelaşi timp programul afişează
camerele vizitate. Primul argument al predicatului trecere este punctul iniţial
(start), iar al doilea argument este punctul final. Argumentele pot fi şi variabile.
Al treilea argument în predicatul trecere1 este lista în care se inserează
camerele ”vizitate”. Al patrulea argument este lista în care se inserează calea
parcursă.
La interogarea

70
Goal trecere (e,d)
se răspunde: [e,d]
[e,b,c,d]
adică se afişează toate soluţiile.

11.3. Staţia de telefoane


Programarea logică oferă un şir de facilităţi pentru lucrul cu baze de date
relaţionale. Programul “Telefoane” (fig.11) utilizează baza de date dinamică în
care se inserează date despre abonaţi:
(Nume, Prenume, Sector, Strada/Bulevard, Bloc, Apartament, Oficiul_post, Tel)
Programul admite operaţii de adăugare şi selectare a datelor. Selectarea se face
în baza unor date incomplete. Presupunem că ne interesează numărul de telefon
al unei persoane şi nu cunoaştem decât prenumele, sectorul şi strada. Atunci
introducem numai valorile acestor atribute. Programul ne va afişa, de regulă, o
listă de abonaţi pentru care valorile atributelor menţionate coincid. Programul
conţine predicate de tipul:
write(”Numarul blocului ? sau Enter”), rdln(Bl).
Pentru opţiunea “Selectare” putem introduce numărul blocului dacă este
cunoscut, în caz contrar acţionăm tasta “Enter”. Atunci când se introduce o
valoare concretă pentru “Numarul blocului”, predicatul rdln are acelaşi efect ca
şi readln. În cazul când nu se introduce o valoare concretă a atributului şi se
acţionează tasta “Enter”, variabila Bl rămâne liberă (free). La fel putem proceda
şi cu alte atribute.
% Programul "Telefoane"
domains
str=string
database
%Nume Prenume Sector Strada Bloc Apart. Oficiu Tel
tel(str, str, str, str, str, str, str, str)
predicates
start init run fin optiune(integer)
repeat rdln(str)
opti1(str, str, str, str, str, str, str, str)
clauses

71
start:- init, run, fin.
init:- tel(_,_,_,_,_,_,_,_),!.
init:- existfile("tel.dat"),consult("tel.dat"),!.
init.
fin:-save("tel.dat").

run:-makewindow(2,24,25,"TELEFOANE",2,23,14,30),
repeat,clearwindow,
write("***************************"),nl,
write("* *"),nl,
write("* 1.Inserare *"),nl,
write("* 2.Selectare *"),nl,
write("* 3.Iesire *"),nl,
write("* *"),nl,
write("* Introdu 1,2 sau 3 *"),nl,
write("* *"),nl,
write("***************************"),nl,nl,
readint(N),optiune(N),N=3,!.
optiune(1):-write("Adaugam?(y/n)"),readchar(X),
X='y',nl,
write("Numele? "),readln(Num),
write("Prenumele? "),readln(Pre),
write("Sectorul? "),readln(Sec),
write("Strada ? "),readln(Str),
write("Blocul? "),readln(Bl),
write("Apartamentul? "),readln(Ap),
write("Oficiul? "),readln(P),
write("Telefonul? "),readln(Tel),
assert(tel(Num,Pre,Sec,Str,Bl,Ap,P,Tel)).
optiune(2):-
makewindow(3,28,7,"Selectare",1,1,23,75),
write("Numele? Sau Enter "),rdln(Num),nl,
write("Prenumele? Sau Enter "),rdln(Pre),nl,
write("Sectorul? Sau Enter "),rdln(Sec),nl,
write("Strada? Sau Enter "),rdln(Str),nl,
write("Blocul? Sau Enter "),rdln(Bl),nl,
write("Apartament? Sau Enter "),rdln(Ap),nl,
write("Oficiul? Sau Enter "),rdln(P),nl,
write("Telefonul? Sau Enter "),rdln(Tel),nl,
nl, writef("%-10%-10%-10%-10%-5%-5%-9%-10",
"Nume","Pre","Sec","Str","Bl","Ap","Of","Tel"),
nl,nl, opti1(Num,Pre,Sec,Str,Bl,Ap,P,Tel).
optiune(_).
opti1(Num,Pre,Sec,Str,Bl,Ap,P,Tel):-
tel(Num,Pre,Sec,Str,Bl,Ap,P,Tel),

72
writef("%-10%-10%-10%-10%-5%-5%-9%-10",
Num,Pre,Sec,Str,Bl,Ap,P,Tel),nl,
fail.
opti1(_,_,_,_,_,_,_,_):-readchar(_),removewindow.
rdln(X):-readchar(C),char_int(C,I),I<>13, write(C),
readln(Z),str_char(Y,C),concat(Y,Z,X),!.
rdln(_).
repeat.
repeat:-repeat.

goal start.
Fig.11 Programul ”Telefoane”.

11.4. Problema "Maimuţa şi banana"


O maimuţă este închisă într-o cuşcă. O banană este prinsă de tavan cu un
fir. De pe podea maimuţa nu ajunge banana. Însă în cuşcă se află o cutie pe care
maimuţa poate s-o deplaseze şi pe care se poate căţăra pentru a apuca banana.
Să descriem în Prolog acţiunile maimuţei care i-ar permite să apuce
banana. Poziţia maimuţei în cuşcă poate să difere de cea a cutiei. Iar poziţia
cutiei poate să difere de cea a bananei. Maimuţa poate să apuce banana numai
atunci când maimuţa, cutia şi banana se află în aceeaşi poziţie. Considerăm că în
cuşcă sunt 10 poziţii.

Programul "Maimuţa şi banana" (fig.12) conţine predicatele:


pozitia_initiala. Stabileşte poziţia maimuţei, cutiei şi a bananei.
(Aceste poziţii se inserează în baza dinamică).
deplasare. Determină poziţia cutiei şi a maimuţei pentru ca maimuţa să se
deplaseze spre cutie.
impinge. Determină poziţia bananei şi a cutiei pentru ca maimuţa să împingă
cutia în dreptul bananei.
urca_pe. Verifică dacă maimuţa, cutia şi banana se află pe aceeaşi poziţie.
Maimuţa se caţără pe cutie.
apuca_banana. Maimuţa apucă banana.

73
database
poz(symbol,integer)

predicates
maimuta_si_banana pozitia_initiala la_treaba
deplasare impinge urca_pe apuca_banana
pauza p

clauses
maimuta_si_banana:-clearwindow,retractall(poz(_,_)),
pozitia_initiala,la_treaba.
maimuta_si_banana:-write("FARA SUCCES"),pauza.

pozitia_initiala:-nl,write("Pozitia bananei?(1-10)"),
readint(Ba),Ba>0,Ba<11,
assert(poz(banana,Ba)),nl,
write("Pozitia maimutei?(1-10)"),
readint(Ma),Ma>0,Ma<11,
assert(poz(maimuta,Ma)),nl,
write("Pozitia cutiei?(1-10)"),
readint(Box),Box>0,Box<11,
assert(poz(box,Box)),!.

la_treaba:-deplasare,impinge,urca_pe,apuca_banana.

deplasare:-poz(maimuta,Ma),poz(box,Box),Ma<>Box,
nl,write("Maimuta se deplaseaza spre
cutie! ",Ma,"->",Box),
retract(poz(maimuta,_)),
assert(poz(maimuta,Box)),pauza,!.
deplasare.

impinge:-poz(banana,Ba),poz(maimuta,Ma),Ba<>Ma,nl,
write("Maimuta impinge cutia! ",Ma,"->",Ba),
retract(poz(maimuta,_)),retract(poz(box,_)),
assert(poz(maimuta,Ba)),assert(poz(box,Ba)),
pauza,!.
impinge.

urca_pe:-poz(box,X),poz(banana,X),poz(maimuta,X),
nl,write("Maimuta urca pe cutie!"),pauza,!.
urca_pe:-nl,write("FARA SUCCES!"),pauza,fail.

apuca_banana:-nl,nl,write("MAIMUTA APUCA BANANA!!!."),


p,p,pauza.

74
pauza:-beep,cursor(X,Y),shiftwindow(N),
makewindow(12,109,132,"",21,55,3,23),
write("Actioneaza o tasta"),readchar(_),
removewindow,shiftwindow(N),cursor(X,Y).

p:-sound(20,139),sound(35,147),sound(20,165).

goal maimuta_si_banana.

Fig.12. Programul "Maimuţa şi banana".


Exerciţii.
1. Să se completeze programul “Semigrup” cu predicate care ar stabili dacă
mulţimea M în raport cu legea de compoziţie este:
a) monoid;
b) grup;
c) grup abelian.
2. Admitem x un element necunoscut (vezi programul “Semigrup”). Să se scrie
un program care ar înlocui elementul x din faptele t(…x…) cu un element din M
astfel încât M să fie un grup. Programul trebuie să indice dacă a reuşit sau nu să
se facă substituţia.
3. Baza de cunoştinţe conţine fapte de forma arc1(A,B,D) unde A şi B sunt
localităţi, iar D este distanţa dintre ele. Deci, în baza de cunoştinţe este
reprezentat un graf. Scrieţi un program Prolog care determină calea cea mai
scurtă dintre localităţile X şi Y. Afişaţi calea (lista) şi distanţa dintre X şi Y.
4. Presupunem că baza de cunoştinţe conţine fapte:
muchie(a,1,5). muchie(a,5,1).
muchie(b,5,4). muchie(b,4,5).
. . . . . . . . . . . . . .
care descriu următorul graf:
1 d 2

a f 3

5 h e c

b 4

75
Scrieţi un program Prolog care pentru un vârf specificat caută un traseu astfel
încât să parcurgă toate muchiile o singură dată şi să revină la vârful specificat.
Afişaţi traseul parcurs.
5. Sunt date congruenţele: a=b, b=c, c=d, d=x, … Scrieţi un program Prolog
care determină dacă are loc congruenţa x=y şi afişaţi soluţia.
6. La programul “Telefoane” să se adauge opţiunile “Modificare” şi
“Eliminare”. Opţiunea “Modificare” permite modificarea valorilor atributelor, ca
exemplu numărul telefonului, numele, prenumele ş.a. Opţiunea “Eliminare”
permite eliminarea unui fapt (tuplu) din baza de date atunci când sunt introduse
corect valorile tuturor atributelor.
La ieşire programul afişează:
s-au adăugat . . . . . abonati
s-au modificat . . . . . tupluri
s-au eliminat . . . . . tupluri
Pentru înregistrarea numărului de modificări se poate utiliza baza de date
dinamică.
7. Presupunem că pentru unele numere de telefon s-au modificat primele două
cifre pe care nu le cunoaştem. Celelalte cifre sunt cunoscute. Să se scrie un
program care caută numărul actual de telefon cunoscându-se câteva cifre şi
valorile unor atribute. La introducerea numărului de telefon în locul cifrelor
necunoscute se scrie caracterul 'X' . De exemplu ”XX2331”.
8. Admitem că baza dinamică pentru programul “Telefoane” pe lângă faptul
tel() mai conţine şi fapte de forma:
impuls( Nr_tel, [im(L,Z,N), im(L,Z,N), …] ).
unde
L – luna;
Z – ziua;
N – numărul de impulsuri.
De exemplu:
impuls(557766,[im(5,25,10),im(6,20,5),im(5,7,21)]).

76
Să se scrie un program care:
– adaugă fapte impuls( );
– afişează suma impulsurilor pentru luna dată şi telefonul dat;
– pentru luna şi telefonul dat afişează informaţia despre impulsurile
înregistrate. De exemplu, pentru luna a cincia afişează:
oficiul postal: MD …
tel: 557766
luna: mai
total impulsuri: 31
9. Presupunem că avem două urcioare ce nu sunt marcate, unul fiind de 8 litri,
altul de 5 litri. Urcioarele pot fi umplute cu apă sau golite. De asemenea, se
poate turna apa dintr-un urcior în altul până când primul se deşartă sau al doilea
se umple. Se pune problema de a acumula într-unul din urcioare exact 4 litri. Să
se scrie un program ce ar realiza acest lucru.
Menţionăm că de la început este necesar să se analizeze spaţiul stărilor
posibile:
(0,0) – ambele urcioare sunt goale;
(8,0) – primul urcior este plin, al doilea gol;
(0,5) – al doilea este plin, iar primul gol.
Examinaţi celelalte stări posibile. Starea finală poate fi: (4, X) sau (X, 4).

77
12. Baze de date externe
Conceptul baze de date poate fi definit ca nişte colecţii de obiecte
grupate conform unei structuri logice care exprimă relaţiile între acestea.
Dacă o bază de date relaţională conţine una sau mai multe relaţii şi
elementele relaţiei sunt tupluri (cortegii), atunci o bază de date externă (Prolog)
reprezintă unul sau mai multe lanţuri. Elementele unui lanţ se numesc termeni.
Termenii pot fi comparaţi cu faptele din bazele de date interne. Termenii din
lanţuri sunt legaţi prin legături (pointeri) foreward şi backward. Termenii pot
să aparţină unui domeniu compus. Fiecărui termen din baza de date externă
sistemul îi ataşează un număr de referinţă care are forma k unde k este un număr
natural. Tipul numărului de referinţă este ref. Pentru a ne referi la un anumit
obiect(termen) din bază ne folosim de numărul de referinţă. Acest număr este
"adus" în program de către predicatele standarde.
Unei baze externe i se poate ataşa unul sau mai mulţi B+ arbori care
simplifică procesul de căutare a datelor.
O bază de date externă este caracterizată printr-un nume logic numit
selector şi un nume fizic (exterior) de tipul string. Selectorul bazei de date este
de tip db_selector.
Iniţial o bază externă este creată şi pentru utilizarea ulterioară ea trebuie
deschisă. La terminarea procesării bazei, aceasta se închide.

12.1. Crearea, deschiderea şi închiderea unei baze externe


Prezentăm predicatele standard care permit crearea, deschiderea şi
închiderea unei baze de date externe.
Utilizăm următoarea convenţie:
1. În prima linie după numele predicatului scriem numele argumentelor sub
formă de variabile. Pentru unul şi acelaşi tip de argument se foloseşte acelaşi
identificator:
Dbase – selectorul bazei;
Name – numele bazei pe suport exterior;

78
Place – poate fi sau in_memory sau in_file.
În primul caz baza se creează sau se deschide în RAM, în al doilea caz se
creează sau se deschide pe un suport exterior.
2. Pe linia a doua se scriu tipurile argumentelor şi prototipul, apoi urmează
descrierea succintă a semanticii predicatului.
- db_create(Dbase, Name, Place)
(db_selector, string, place) - (i, i, i)
Creează o bază externă. db_create suplineşte deschiderea bazei.

- db_open(Dbase, Name, Place)


(db_selector, string, place) - (i, i, i)
Deschide o bază externă.

- db_openinvalid(Dbase, Name, Place)


(db_selector, string, place) - (i, i, i)
Deschide o bază externă după avarie. Rezultatul este imprevizibil.

- db_close(Dbase)
(db_selector) - (i)
Închide o bază externă.

- db_delete(Name, Place)
(string, place) - (i, i)
Şterge baza din Place.

- db_flush(Dbase)
(db_selector) - (i)
Transferă conţinutul zonei buffer în bază.

- db_garbagecollect(Dbase)

79
(db_selector) - (i)
Comprimă spaţiile libere din bază.

- db_copy(Dbase, Name, Place)


(db_selector, string, place) - (i, i, i)
Copie o bază deschisă. În urma copierii baza este comprimată.
- db_chains(Dbase, Chain)
(db_selector, string) - (i, o)
Returnează în ordine alfabetică numele lanţurilor dintr-o bază de date.

- db_btrees(Dbase, Btree)
(db_selector, string) - (i, o)
Returnează în ordine alfabetică numele arborilor unei baze externe.

- db_statistics(Dbase, NoOfTerms, MemSize, DbaSize, FreeSize)


(db_selector, real, real, real, real) - (i, o, o, o, o)
Aduce date statistice:
a) numărul termenilor;
b) memoria;
c) mărimea bazei de date;
d) spaţiu liber disponibil.

12.2. Operaţii asupra lanţurilor


Sunt mai multe predicate pentru operarea cu lanţuri. Aceste predicate
asigură:
- inserarea termenilor în lanţ;
- deplasarea într-un lanţ;
- înlocuirea unor termeni;
- eliminarea unor termeni sau a lanţului în întregime;
- extragerea termenilor din lanţ.

80
Pentru extragerea termenilor sunt folosite două predicate chains_terms şi
ref_term. Numele unui lanţ este definit în predicatele de inserare, adică nu este
definit anterior.
Termenii dintr-un lanţ pot aparţine unor domenii diferite. Lucrul cu
lanţurile, de regulă, începe cu aducerea pointerului la primul sau la ultimul
element (termen) din lanţ.
La prezentarea predicatelor standard utilizăm aceleaşi convenţii ca şi în
paragraful precedent. Aici mai utilizăm identificatorii:
Domain - numele domeniului;
Chain - numele lanţurilor;
Term - variabilă. Numele termenului sau termenul;
FirstRef - variabilă. Numărul de referinţă al primului termen din
lanţ;
LastRef - variabilă. Numărul de referinţă al ultimului termen din
lanţ.
Predicatele de lucru cu lanţurile sunt:

- chain_inserta(Dbase, Chain, Domain, Term, Ref)


(db_selector, string, symbol, <Domain>, ref) - (i, i, i, i, o)
Inserează Term în Dbase la începutul lanţului Chain. Aduce numărul de referinţă
Ref.

- chain_insertz(Dbase, Chain, Domain, Term, Ref)


(db_selector, string, symbol, <Domain>, ref) - (i, i, i, i, o)
Inserează Term în Dbase la sfârşitul lanţului Chain. Aduce numărul de
referinţă Ref.

- chain_insertafter(Dbase, Domain, Ref, Term, NewRef)


(db_selector, symbol, ref, <Domain>, ref) - (i, i, i, i, o)

81
Inserează Term în Dbase după elementul determinat de Ref. Aduce numărul
de referinţă NewRef.

- chain_delete(Dbase,Chain)
(db_selector, string) - (i, i)
Şterge lanţul Chain din Dbase.

- chain_terms(Dbase, Chain, Domain, Term, Ref)


(db_selector, string, symbol, <Domain>, ref) - (i, i, i, _, o)
Aduce termenul curent şi numărul de referinţă din lanţul Chain. Prin revenire
(backtracking) sunt obţinuţi toţi termenii.

- chain_first(Dbase, Chain, FirstRef)


(db_selector, string, ref) - (i, i, o)
Aduce numărul de referinţă al primului termen din lanţ.

- chain_last(Dbase, Chain, LastRef)


(db_selector, string, ref) - (i, i, o)
Aduce numărul de referinţă al ultimului termen din lanţ.

- chain_next(Dbase, Ref, NextRef)


(db_selector, ref, ref) - (i, i, o)
Aduce numărul de referinţă al următorului termen din lanţ.

- chain_prev(Dbase, Ref, PrevRef)


(db_selector, ref, ref) - (i, i, o)
Aduce numărul de referinţă al elementului anterior din lanţ.

- term_delete(Dbase, Chain, Ref)


(db_selector, string, ref) - (i, i, i)

82
Şterge un termen.

- term_replace(Dbase, Domain, Ref, NewTerm)


(db_selector, symbol, ref, <Domain>) - (i, i, i, i)
Înlocuieşte termenul cu numărul de referinţă Ref cu un nou termen NewTerm.

- ref_term(Dbase, Domain, Ref, Term)


(db_selector, symbol, ref, <Domain>)-(i, i, i, _)
Aduce un termen de referinţă dată.

Exemplu. Lucrul cu lanţuri.


Să creăm o bază externă formată dintr-un singur lanţ cu numele lant_st. Acest
lanţ conţine obiecte (termeni) de forma:
stud(Nr_student, Nume, Facultate, Grupa, Localitatea)
Argumentele sunt de tip string. Programul este prezentat în fig. 13.
Predicatul add_st inserează termeni în lanţul lant_st. Predicatul sel_st
selectează studenţii din grupa dată. Mai întâi predicatul aduce pointerul la
începutul lanţului. Cu ref_term extragem termenii. Subscopul
Term = stud(_, Nume, _, Gr, _)
selectează termenii pentru grupa dată. Predicatul chain_next deplasează
pointerul la următorul element din lanţ.

domains
int=integer s=symbol
db_selector=studii
dom_st=stud(int,s,s,s,s) %Nr,Name,Fac,Gr,Loc

predicates
start meniu proces(int)
deschide_bd inchide_bd add_st
sel_st sel_st1(db_selector,s,ref) r

83
clauses

start:-deschide_bd,meniu,inchide_bd.
meniu:-makewindow(2,28,96,"Meniu",5,10,17,35),
r, clearwindow,
write(" ******************************"),nl,
write(" * *"),nl,
write(" * 1. Inserare termen. *"),nl,
write(" * 2. Selectare. *"),nl,
write(" * 3. Iesire. *"),nl,
write(" * *"),nl,
write(" ******************************"),nl,
nl,write(" Introdu 1,2 sau 3"),nl,
readint(N),proces(N),N=3,!.

proces(1):-add_st,!.
proces(2):-sel_st,readchar(_),!.
proces(_).

deschide_bd:-existfile("stud_bd"),
db_open(studii,"stud_bd",in_file),!.
deschide_bd:-db_create(studii,"stud_bd",in_file).
inchide_bd:-db_close(studii).

% Adaugare
add_st:-write(" Adaugam? (y/n)"),readchar(X),
X='y',nl,
write("Nr_stud? "), readint(Nr),
write("Nume? "), readln(Num),
write("Facultate? "), readln(Fac),
write("Grupa? "), readln(Gr),
write("Localitatea? "),readln(Loc),nl,
Term=stud(Nr,Num,Fac,Gr,Loc),
chain_insertz(studii,lant_st,dom_st,Term,_),
add_st.
add_st:-db_flush(studii).

% Selectare
sel_st:-write("Grupa? "),readln(Gr),nl,
chain_first(studii,lant_st,Ref),
sel_st1(studii,Gr,Ref).

sel_st1(DB,Gr,Ref):-ref_term(DB,dom_st,Ref,Term),
Term=stud(_,Nume,_,Gr,_),
write(Nume," ",Gr),nl,fail.

84
sel_st1(DB,Gr,Ref):-chain_next(DB,Ref,NewRef),
sel_st1(DB,Gr,NewRef).
sel_st1(_,_,_).

r.
r:-r.

goal start.

Fig.13. Crearea şi prelucrarea unui lanţ.

12.3. Lucrul cu B+ arbori


Un B+ arbore este o structură a bazei de date utilizată pentru regăsirea
rapidă a unui termen într-un lanţ. În arbore se inserează perechile
cheie+referinţă. Întrucât arborele este B+ arbore, toate aceste perechi se conţin
în nodurile terminale. Dacă, de exemplu, ordinul este 8, atunci nodurile
terminale conţin cel puţin 8 şi cel mult 16 perechi cheie-referinţă. Lungimea
cheii se stabileşte la crearea arborelui. O cheie are o lungime fixă, cheile mai
lungi se trunchiază. Două obiecte diferite din bază pot avea chei diferite sau pot
avea aceeaşi cheie. Crearea arborilor, precum şi inserarea sau eliminarea
elementelor din arbori, este asigurată de către utilizator. Unei baze de date i se
poate ataşa unul sau mai mulţi B+ arbori. Numele arborelui este definit în
predicatul bt_open. B+ arborele ca şi baza de date trebuie creat, deschis şi
închis. La creare sau la deschidere sistemul întoarce un număr care este
selectorul (sau indicele) arborelui. Acest indice (număr) are tipul bt_selector.
Indicele arborelui se utilizează la închiderea arborelui şi în alte predicate. Pentru
ca acest indice să nu-l transmitem de la predicat la predicat, cu ajutorul
argumentelor este bine să-l memorăm în baza de date internă. La prezentarea
predicatelor standarde utilizăm aceleaşi convenţii ca şi în paragrafele precedente
plus identificatorii:
BtreeName – numele B+ arborelui;
Btree_selector – selectorul (indicele) arborelui;
KeyLen – lungimea cheii;

85
Order – ordinul arborelui (4, 8, …).
Predicatele de lucru cu B+ arborii sunt:

- bt_create(Dbase, BtreeName, Btree_selector, KeyLen, Order)


(db_selector, string, bt_selector, integer, integer) - (i, i, o, i, i)
Creează un B+ arbore în baza Dbase. Al doilea argument easte numele arborelui.

- bt_open(Dbase, BtreeName, Btree_selector)


(db_selector, string, bt_selector) - (i, i, o)
Deschide un B+ arbore al bazei Dbase.

- bt_close(Dbase, Btree_selector)
(db_selector, bt_selector) - (i, i)
Închide arborele cu indicele Btree_selector.

- bt_delete(Dbase, BtreeName)
(db_selector, string) - (i, i)
Şterge arborele cu numele BtreeName.

- bt_statistics(Dbase, Btree_selector, NoOfKeys, NoOfPages, Dept,


KeyLen, Order, PageSize)
(db_selector, bt_selector, real, real, integer, integer, integer, integer) –
(i, i, o, o, o, o, o, o)
Indică informaţiile statistice referitoare la un arbore.

- key_insert(Dbase, Btree, Key, Ref)


(db_selector, bt_selector, string, ref) - (i, i, i, i)
Inserează o cheie (Key+Ref) în arbore.

- key_delete(Dbase, Btree, Key, Ref)

86
(db_selector, bt_selector, string, ref) - (i, i, i, i)
Şterge o cheie din arbore.

- key_first(Dbase, Btree, FirstRef)


(db_selector, bt_selector, ref) - (i, i, o)
Indică referinţa pentru prima cheie din arbore.

- key_last(Dbase, Btree, LastRef)


(db_selector, bt_selector, ref) - (i, i, o)
Indică referinţa pentru ultima cheie din arbore.

- key_search(Dbase, Btree, Key, Ref)


(db_selector, bt_selector, string, ref) - (i, i, i, o)
Caută o cheie. Indică referinţa pentru cheia dată.

- key_next(Dbase, Btree, NextRef)


(db_selector, bt_selector, ref) - (i, i, o)
Mută pointerul la cheia următoare şi indică referinţa la ea. Dacă nu există
următoarea cheie, pointerul rămâne în exteriorul arborelui.

- key_prev(Dbase, Btree, PrevRef)


(db_selector, bt_selector, ref) - (i, i, o)
Mută pointerul la cheia anterioară şi indică referinţa la ea. Dacă nu există o
cheie anterioară, pointerul rămâne în exteriorul arborelui.

- key_current(Dbase, Btree, Key, Ref)


(db_selector, bt_selector, string, ref) - (i, i, o, o)
Indică cheia curentă şi numărul de referinţă (din poziţia curentă a pointerului
arborelui).

87
Acest predicat întoarce fail, dacă pointerul este în afara arborelui sau
dacă este utilizat după unul din predicatele: bt_open, bt_create, key_insert,
key_delete.
Exemplu. Aplicaţie a bazelor de date externe.
Vom crea o bază formată din două lanţuri. Programul este prezentat în
fig.14. Primul lanţ este lanţul din exemplul anterior (fig.13). Al doilea lanţ are
numele lant_ex şi conţine termeni de forma:
examen(Nr_student, Disciplina, Nota).
Primului lanţ îi ataşăm arborele cu numele ar_st, iar lanţului lant_ex – arborele
ar_ex. Pentru arborele ar_st cheia este grupa studentului, iar pentru arborele
ar_ex cheia este numărul studentului. Întrucât într-o grupă sunt mai mulţi
studenţi şi un student susţine mai multe examene, ambii arbori au chei multiple.
Predicatul add_st inserează termeni în lanţul lant_st, iar cheile în arborele
ar_st. Analogic predicatul add_ex inserează termeni în lanţul lant_ex, iar cheile
– în arborele ar_ex.
Predicatul sel_st aduce pointerul la prima cheie din arborele pentru grupa
dată. Extrage din arborele ar_st numărul, iar din lanţul lant_st numele
studentului. Numărul şi numele se transmit predicatului caut_ex, care cu ajutorul
arborelui ar_ex caută disciplinele şi notele din lanţul lant_ex.
domains
int=integer s=symbol
db_selector=studii
dom_st=stud(s,s,s,s,s) % Nr,Name,Fac,Gr,Loco
dom_ex=examen(s,s,int) % Nr,Disciplina,Nota

database
index(s,bt_selector)

predicates
start meniu proces(int)
deschide_bd inchide_bd add_st add_ex
sel_st sel_st1(s,bt_selector) r
caut_ex(s,s) caut_ex1(s,s,bt_selector)

88
clauses
start:-retractall(index(_,_)),
deschide_bd,meniu,inchide_bd,exit.

meniu:-makewindow(2,28,96,"Meniu",2,10,20,35),
r,clearwindow,
write(" ******************************"),nl,
write(" * *"),nl,
write(" * 1. Inserare _stud. *"),nl,
write(" * 2. Inserare_examen *"),nl,
write(" * 3. Selectare (Gr_examen). *"),nl,
write(" * 4. Iesire. *"),nl,
write(" * *"),nl,
write(" ******************************"),nl,
nl,write(" Introdu 1,2,3 sau 4"),nl,
readint(N),proces(N),N=4,!.

proces(1):-add_st,!.
proces(2):-add_ex,!.
proces(3):-sel_st,readchar(_),!.
proces(_).

deschide_bd:-existfile("stud_bd"),
db_open(studii,"stud_bd",in_file),
bt_open(studii,ar_st,I1),
assert(index(ar_st,I1)),
bt_open(studii,ar_ex,I2),
assert(index(ar_ex,I2)),!.
deschide_bd:-db_create(studii,"stud_bd",in_file),
bt_create(studii,ar_st,I1,10,4),
assert(index(ar_st,I1)),
bt_create(studii,ar_ex,I2,8,8),
assert(index(ar_ex,I2)).
inchide_bd:-index(ar_st,X),index(ar_ex,Y),
bt_close(studii,X),bt_close(studii,Y),
db_close(studii).

% Adaugam un student
add_st:-write(" Adaugam? (y/n)"),readchar(X),
X='y',nl,nl,
write("Nr_stud? "), readln(Nr),
write("Nume? "), readln(Num),
write("Facultate? "), readln(Fac),
write("Grupa? "), readln(Gr),
write("Localitatea? "), readln(Loc),nl,

89
Term=stud(Nr,Num,Fac,Gr,Loc),
chain_insertz(studii,lant_st,dom_st,Term,Ref),
index(ar_st,I),key_insert(studii,I,Gr,Ref),
add_st.

add_st:-db_flush(studii).

% Adaugam un examen
add_ex:-write(" Adaugam? (y/n)"),readchar(X),
X='y',nl,nl,
write("Nr_stud? "), readln(Nr),
write("Disciplina? "), readln(D),
write("Nota ? "), readint(Nota),
Nota>0,Nota<=10,
Term=examen(Nr,D,Nota),
chain_insertz(studii,lant_ex,dom_ex,Term,Ref),
index(ar_ex,I),key_insert(studii,I,Nr,Ref),
add_ex.

add_ex:-db_flush(studii).

% Selectare: Gr - Nr_st
sel_st:-write("Grupa? "),readln(Gr),nl,
writef("%5%9%9%5\n"," Nr"," Nume",
" Disciplina"," Nota"),
index(ar_st,I),key_first(studii,I,_),
key_search(studii,I,Gr,_),
sel_st1(Gr,I).

sel_st1(Gr,I):-key_current(studii,I,G,R),G=Gr,
ref_term(studii,dom_st,R,Term),
Term=stud(Nr,Num,_,_,_),
caut_ex(Nr,Num),fail.

sel_st1(Gr,I):-key_next(studii,I,_),sel_st1(Gr,I).

sel_st1(_,_).

% Selectare: Nr_st - Nota


caut_ex(Nr,Num):-index(ar_ex,I),
key_first(studii,I,_),
key_search(studii,I,Nr,_),
caut_ex1(Nr,Num,I).

90
caut_ex1(Nr,Num,I):-key_current(studii,I,N,R),
N=Nr,
ref_term(studii,dom_ex,R,Term),
Term=examen(Nr,D,Nota),
writef("%5%9%9%5\n",Nr,Num,D,Nota),
fail.

caut_ex1(Nr,Num,I):-key_next(studii,I,_),
caut_ex1(Nr,Num,I).

caut_ex1(_,_,_).

r.
r:-r.

goal start.

Fig.14. Crearea şi prelucrarea arborilor B+.

91
Bibliografie
[1] Cloksin W.E and Mellish C.S., Programming in Prolog, 2 nd Edition,
Springer Verlag, New York, 1984.
[2] Cotelea V., Programarea în logică, UŞAM, Chişinău, 2000.
[3] Date C.J., A guide to DB2, Mass.: Addison-Wesley Publishing
Company, 1984.
[4] Malpas J., Prolog. A Relational Language and its Applications,
Prentice-Hall International, Inc, 1987.
[5] Meszaros Judith, Turbo Prolog 2.0 Ghid de utilizare, Editura
Albastră, Cluj-Napoca, 1996.
[6] Sterling L. and E. Sapiro, The Art of Prolog, MIT-Press, 1986.
[7] Ţândăreanu N., Introducere în Programarea logică. Limbajul Prolog,
Editura"Intarf", Craiova, 1994.
[8] Yin K.M, Using Turbo Prolog. Que Corporation, Indiana, 1987.

92

You might also like