Professional Documents
Culture Documents
INTRODUCERE
ÎN PROGRAMAREA LOGICĂ
CICLU DE PRELEGERI
&
- Chişinău 2003 -
0
Prefaţă
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.
4
Să demonstrăm A. Pentru aceasta adăugăm clauza A. Atunci:
1) BA 1) B
2) CB 2) CB 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.
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.
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
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ă.
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).
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).
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
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.
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).
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).
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.
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
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.
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).
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).
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
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ă
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]
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.
insertie(X,[],[X]).
insertie(X,[Y|Ls],[Y|Zs]):-X>Y,
insertie(X,Ls,Zs).
insertie(X,[Y|Ls],[X,Y|Ls]):- X<=Y.
39
La interogarea Goal: insertie (3,[1,2,4,5],Y)
răspunsul va fi Y=[1,2,3,4,5]
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)
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=L1L2 ş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.
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.
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
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.
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
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.
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)]
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.
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").
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").
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(_,_):-!.
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ă.
b d
c
e f g h
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.
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
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”.
70
Goal trecere (e,d)
se răspunde: [e,d]
[e,b,c,d]
adică se afişează toate soluţiile.
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”.
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.
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.
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.
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_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_btrees(Dbase, Btree)
(db_selector, string) - (i, o)
Returnează în ordine alfabetică numele arborilor unei baze externe.
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:
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.
82
Şterge un termen.
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.
85
Order – ordinul arborelui (4, 8, …).
Predicatele de lucru cu B+ arborii sunt:
- 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.
86
(db_selector, bt_selector, string, ref) - (i, i, i, i)
Şterge o cheie din arbore.
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(_,_).
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.
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