Sunteți pe pagina 1din 25

Introducere practic n Prolog (I)

n programarea logic, ideea de baz este exprimat prin Algorithm = Logic + Control. Componenta Logic - asimilat cu "program Prolog", sau "baz de date" - vizeaz descrierea problemei: ce fapte se cunosc (aseriuni iniiale asupra obiectelor problemei) i ce reguli generale ("parametrizate" fa de obiectele problemei) trebuie folosite pentru a deduce ulterior alte fapte (noi). Componenta Control reprezint nsui interpretorul (ori fiina raional) care va "executa" programele; interpretorul de Prolog "aplic" metoda deducerii (plecnd de la baza de date furnizat de program) i un mecanism intern de backtracking recursiv - cutnd sistematic valori cu care s "nlocuiasc" necunoscutele implicate n scopul ("goal") satisfacerii clauzelor curente. Sunt n uz diverse implementri ale componentei "Control" (vezi Prolog Systems) i n mare ele respect un acelai standard (ISO Prolog). Aici avem n vedere i utilizm SWI-Prolog.

O exemplificare clasic: relaii de rudenie


n privina ordinii exemplificrilor cuprinse n lucrri introductive de Prolog - prezentarea unei baze de date pe o tem larg cunoscut - cum ar fi, relaiile de rudenie - este clasic (la fel cu programele "Hello world!" cu care debuteaz prezentarea n alte limbaje). Prinii lui Fred sunt Ed i Edna (vezi The Flintstones); Pebbles este fiica lui Fred; etc.
man('Fred'). man('Ed').
% exprim faptul c "Fred este brbat"

woman('Edna'). % Edna este femeie woman('Wilma'). woman('Pebbles'). parent('Ed', 'Fred'). % Fred este un copil al lui Ed parent('Edna', 'Fred'). parent('Fred', 'Pebbles'). parent('Wilma', 'Pebbles'). grandparent(X, Z) :% X este bunic/bunic a lui Z dac (notat :-): parent(X, Y) % exist Y nct este valabil parent(X, Y) , % i (virgula conjug clauzele) parent(Y, Z). % este satisfcut parent(Y, Z). Am declarat nite fapte - man('Fred', woman('Wilma') etc. - nite relaii - parent(Ed,Fred) etc. - i o regul (pentru a fi "grandparent"). Caracterul . (denumit "full-point") ncheie declaraia.

'Fred' - cu apostrof - este o constant (alternativa era fred, cu litere mici dar fr ncadrare cu apostrof); Fred - cu majuscul iniial i nencadrat de apostrof - ar fi interpretat ca o variabil (cum sunt mai sus X, Y, Z). Interpretorul va substitui o variabil cu un obiect sau altul, dup caz. S lansm interpretorul SWI-Prolog, swipl:
vb@vb:~/docere/doc/Prolog$ swipl % library(swi_hooks) compiled into pce_swi_hooks 0.00 sec, 2,224 bytes Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.10.1) Copyright (c) 1990-2010 University of Amsterdam, VU Amsterdam SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. Please visit http://www.swi-prolog.org for details.

For help, use ?- help(Topic). or ?- apropos(Word). ?-

Fie "flintstones.pl" fiierul n care am salvat declaraiile de mai sus; la promptul ?- tastm:
?- [flintstones]. % flintstones compiled 0.00 sec, 1,604 bytes true. ?-

Interpretorul a ncrcat i a compilat fiierul respectiv, afind din nou promptul. Putem pune ntrebri (interogri, "queries") i putem formula diverse scopuri (similar cu "program main()").
?- man('Fred'). true. % acest fapt exist n baza de date ?- man('Mister'). false. % Nu exist (i nici nu poate fi dedus) ?- man(Fred). % aici Fred este o variabil! Fred = 'Fred' ; % se tasteaz ; pentru a obine rspunsuri alternative Fred = 'Ed'. ?-

Interogarea man(Fred) - unde Fred este o variabil (puteam folosi la fel de bine man(X), de exemplu) are drept rspunsuri toate valorile cu care se poate nlocui "necunoscuta" Fred astfel nct s fie

satisfcut clauza "man". Interpretorul caut o potrivire n baza de date, afieaz rspunsul gsit i apoi ateapt: dac se tasteaz ; (pentru "sau" soluie alternativ) atunci reia cutarea. n momentul ncrcrii i compilrii programului, interpretorul indexeaz intern termenii respectivi (dup nume i dup ordinea apariiei lor n program) - astfel c acum gsete foarte uor termenul "man" (mpreun cu "ramurile" respective: 'Fred' i 'Ed', n aceast ordine); gsind prima potrivire posibil - anume man(Fred) = man('Fred') - interpretorul marcheaz intern "ramura" respectiv astfel c (dup ce se tasteaz ;) cutarea unei alternative va fi reluat ncepnd din acest loc.
?- parent(X, 'Pebbles'). X = 'Fred' ; % sau X = 'Wilma'.
% Cine sunt prinii lui 'Pebbles'?

?- parent(X, 'Pebbles'), woman(X). X = 'Wilma'.

% Dar cine este mama lui 'Pebbles'?

Pentru ultima interogare de mai sus, interpretorul gsete nti "potrivirea" parent('Fred', 'Pebbles') i consider mai departe valoarea 'Fred' pentru toate apariiile lui X n cadrul interogrii; dar cu aceast instaniere a variabilei X, clauza woman('Fred') este nesatisfcut - "Fail" - i atunci revine - "Redo" - la cealalt "ramur", instaniind X cu 'Wilma':
?- trace. true. [trace] Call: Exit: Call:
% pentru a trasa pas cu pas execuia urmtoarei interogri

?- parent(X, 'Pebbles'), woman(X). (7) parent(_G793, 'Pebbles') ? creep (7) parent('Fred', 'Pebbles') ? creep (7) woman('Fred') ? creep

Fail: (7) Redo: (7) Exit: (7) Call: (7) Exit: (7) X = 'Wilma'.

woman('Fred') ? creep parent(_G793, 'Pebbles') ? creep parent('Wilma', 'Pebbles') ? creep woman('Wilma') ? creep woman('Wilma') ? creep

Dac valorile unuia dintre argumentele prevzute n clauze sunt indiferente, atunci putem folosi o variabil anonim - reprezentat prin _:
[trace] true. ?- notrace.
% Anuleaz trasarea pas cu pas

[debug] ?- parent(X, _). % Care sunt prinii? X = 'Ed' ; % sau (se tasteaz ;) X = 'Edna' ; % sau X = 'Fred' ; % sau X = 'Wilma'. [debug] ?- grandparent(X, 'Pebbles'). X = 'Ed' ; X = 'Edna' ; false.
% Cine sunt bunicii lui 'Pebbles'?

[debug] ?- findall(X, parent(X, _), Result_list). Result_list = ['Ed', 'Edna', 'Fred', 'Wilma']. [debug] ?- findall(X, grandparent(X, _), L).

% List cu prinii

% List cu bunicii

L = ['Ed', 'Edna'].
?- explain(findall) va expune o documentare pentru predicatul predefinit respectiv (aici, findall); pentru documentare se poate folosi i help(findall), dar "explain" indic i fiierul-surs aferent

(uneori, aici gseti cea mai bun lmurire a lucrurilor!).

Am observat mai sus, dar este de subliniat: cutarea n baza de date se face "de sus n jos" - ceea ce nseamn c ordinea clauzelor este important - cu eventual revenire la precedentul punct n care exist alternative neexplorate; n cazul "execuiei" unei clauze (sau interogri) compuse din mai multe subclauze conjugate prin , - ordinea operrii este "de la stnga la dreapta" (iar aici
"operare" nu nseamn "evaluare i atribuire" ca n alte limbaje, ci substituie i unificare, urmrind "potrivirea" final a termenilor).

Exemplificri aritmetice
Prolog a aprut n 1972 i are tangene conceptuale mai degrab cu SQL (unde deasemenea folosim interogri i baze de date relaionale), dect cu limbajele "clasice" (Pascal a aprut n 1970, iar C n 1978). Pentru tratarea serioas a unor aspecte matematice, limbajele "clasice" (ncepnd cu limbajul de asamblare) sunt desigur cele mai potrivite; altfel ns, diverse aspecte aritmetice pot fi tratate n orice limbaj i este interesant de vzut asemenea exemple n Prolog i n SQL.

Cel mai mare divizor comun


Amintim cum s-ar scrie o funcie Pascal care furnizeaz "Gcd":
function GCD(a, b: integer): integer; begin if b = 0 then GCD := a

else GCD := GCD(b, a mod b); end;

Se recunoate desigur, algoritmul lui Euclid i fiind formulat recursiv, poate fi reflectat uor n Prolog (unde recursivitatea este "limba matern"):
gcd(A, B, G) :(B =:= 0 -> G is A ; R is A mod B, gcd(B, R, G) ).
% gcd(A,B) este G dac: % n cazul cnd B are valoarea 0, G = A % altfel, % (R fiind restul mpririi lui A la B), % avem G = gcd(B,R).

Am definit un predicat cu o singur clauz, gcd/3 (de aritate 3, adic avnd 3 argumente). Construcia ( clauz -> clauz_1 ; clauz_2 ). corespunde cu "IF clauz THEN clauz_1 ELSE clauz_2". Pentru cazul valorilor numerice, operatorii prevzui pentru egalitate, inegalitate i "mai mic sau egal" difer ca simbol de alte limbaje: =:= (egalitate), =\= (inegalitate), =< (mai mic sau egal).
R is Expresie asigur evaluarea expresiei aritmetice din partea dreapt i eventual, instanierea variabilei R cu valoarea rezultat; forma echivalent este is(R, Expresie).

Lansm swipl i tastm la promptul interpretorului:


?- [pitagora]. % consult fiierul pitagora.pl % pitagora compiled 0.00 sec, 592 bytes true. ?- listing.
% listeaz definiiile

gcd(C, A, B) :( A=:=0 -> B is C ; D is C mod A, gcd(A, D, B) ). true. ?- gcd(12, 21, G). G = 3.


% o interogare

listing nlocuiete identificatorii de variabile din program cu cte o anumit majuscul.

Dar putem face i probe mai interesante dect am fcut mai sus (pe numerele 12 i 21):
?- X is 3**20 * 5**10 * 7**12, Y is 3**25 * 7**8 * 11**11, gcd(X, Y, G). X = 471304534201247574228515625, % 320 * 510 * 712 Y = 1393590653142003761801219090073, % 325 * 78 * 1111 G = 20100618201669201. % cel mai mare divizor comun ?- G is 3**20 * 7**8. G = 20100618201669201.
% verificare: cel mai mare divizor comun este 320 * 78

Pentru aritmetica numerelor ntregi sau raionale, SWI-Prolog - ca i alte interpretoare, pentru diverse limbaje - folosete biblioteca GMP (scris n C i limbaj de asamblare) - nct putem folosi "gcd" i pentru numere mari, cum am ilustrat mai sus. ns din punctul de vedere al definiiei "gcd", proba cea mai interesant este aceasta:

?- gcd(0, 0, G). G = 0.

Deci se "ncalc" definiia uzual, n care cel mai mare divizor comun este definit cu excepia cazului cnd ambii operanzi sunt nuli. Corect ar fi fost ca gcd(0, 0, G) s fie "false" (nu "G = 0"), la fel cum:
?- gcd(12, 21, 5). false. ?- gcd(12, 21, 3). true.
% "este adevrat c 5 este cel mai mare divizor pentru 12 i 21"? % Nu. % "este adevrat c 3 este cel mai mare divizor pentru 12 i 21"? % Da.

Tratarea acestui caz de excepie (omis i n funcia Pascal de mai sus) ar necesita de obicei instruciuni n plus n cadrul funciei, constituia "clasic" a unei funcii recursive fiind: verific "condiia de oprire"; dac este satisfcut, atunci returneaz un anumit rezultat i "exit" - altfel, determin noii parametri i reapeleaz funcia cu acetia. i pe de o parte, verificarea condiiei de oprire va decurge la fiecare nou reapelare, iar pe de alta - "exit" nseamn "curarea" succesiv a cadrelor-stiv create pe parcursul reapelrilor, revenind "nnapoi" la contextul iniial de apel al funciei recursive respective. Putem imita i n Prolog aceast manier "clasic" de tratare, dar dispunem i de o metod specific, n fond mai "direct" (i posibil, mai eficient): anume, putem specifica diverse cazuri particulare n clauze separate (dar "n cadrul" aceluiai predicat):
gcd(A, 0, A) :- A > 0.
% dac A > 0, atunci gcd(A, 0) este A

gcd(A, B, G) :B > 0, R is A mod B, gcd(B, R, G).

% gcd(A,B) este G dac: % B > 0 (n-a ajuns zero) i % (R fiind restul mpririi lui A la B), % avem G = gcd(B,R).

Acum predicatul "gcd" este format din dou clauze; condiiile A > 0 i B > 0 exclud cazul gcd(0, 0, G) (acum obinem corect "false" i nu valoarea 0). n plus, am reuit astfel (separnd clauzele) s evitm construcia IF-Then-Else (care este n general costisitoare, ca timp de execuie). Lucrurile nu decurg totui dup cum ar fi de dorit:
[trace] ?- gcd(2, 0, 2). Call: (6) gcd(2, 0, 2) ? creep % gsete clauza gcd(A, 0, A), "nlocuind" A cu 2 ^ Call: (7) 2>0 ? creep % "A > 0" ? (din prima clauz) ^ Exit: (7) 2>0 ? creep Exit: (6) gcd(2, 0, 2) ? creep true ; % prima clauz este satisfcut, dar exist alternative (tastm ;) Redo: (6) gcd(2, 0, 2) ? creep % gsete i a doua clauz, cu A <-- 2 i B <-- 0 ^ Call: (7) 0>0 ? creep % "B > 0" ? (din a doua clauz) ^ Fail: (7) 0>0 ? creep Fail: (6) gcd(2, 0, 2) ? creep false. % a doua clauz nu este satisfcut

Avnd scopul gcd(2,0,2), interpretorul a cutat n baza de date curent o declaraie potrivit scopului respectiv i a gsit nti regula cu antetul gcd(A,0,A) (prin nlocuirea "necunoscutei" A cu valoarea 2, capul acestei reguli devine identic scopului de satisfcut). Gsirea unei "potriviri" are de obicei, dou urmri: interpretorul reine intern locul n care a gsito (dac ulterior va fi cazul, va relua cutarea din acel loc) i respectiv, se trece la "executarea"

corpului constituent al regulii gsite. n cazul redat mai sus, se execut (6) i apoi (7), ncheind cu rezultatul "true" verificarea primei reguli gsite. Dar dup ce afieaz acest rspuns, interpretorul trece n starea de ateptare: dac se tasteaz ; atunci interpretorul execut "Redo" - adic reia cutarea din precedentul punct n care a reinut c exist alternative (n cazul de fa, alternativa urmtoare n baza de date este gcd(A,B,G) care se potrivete scopului prin substituia lui A cu 2, a lui B cu 0 i a lui G cu 2). Este clar c execuia ar fi trebuit ncheiat imediat dup verificarea primei reguli. O metod uzual pentru a impune aceasta (influennd din afar, mecanismul intern de backtracking) const n folosirea operatorului denumit cut i notat prin ! (acesta elimin - sau "taie" - punctele de revenire precedente). Prima clauz se rescrie atunci astfel:
% ("cut") ignor alternativele precedentului "Call" Cu aceast mic modificare (am adugat ! n prima clauz "gcd"), dac relum trasarea pentru

gcd(A, 0, A) :- A > 0, !.

gcd(2,0,2) (redat mai sus pentru cazul fr "cut") - constatm c acum execuia se ncheie corect, adic imediat dup constatarea verificrii primei clauze.

Triplete pitagoreice primitive


S gsim triunghiurile dreptunghice distincte care au catetele numere naturale coprime; acestea sunt numite primitive Pythagorean triple (PPT). Vorbim deci de tripletele (a,b,c) de numere ntregi pozitive care satisfac relaiile: a < b, gcd(a, b) = 1 i c2 = b2 + a2 (unde gcd este predicatul definit mai sus, pentru cel mai mare divizor comun).
"a < b" exclude triunghiurile congruente (triplete ca (3,4,5) i (4,3,5)), iar "a i b sunt coprime" exclude asemnarea (de exemplu (3k, 4k, 5k) n care k este un factor arbitrar).

O descriere brut a proprietii PPT poate fi urmtoarea:


ppt(A, B, C) :% (A,B,C) este PPT (dar cu C <= 100) dac: between(5, 100, C), % C are o valoare 5..100 between(4, C, B), % B refer un ntreg 4..C (valoarea curent a lui C) between(3, B, A), % A refer un ntreg 3..B_curent gcd(A, B, 1), % A i B nu au divizori comuni (afar de 1) C^2 =:= A^2 + B^2. % i n final, este verificat teorema lui Pitagora
Privind between/3: ?- help(between)., sau ?- explain(between).

Adugm aceast definiie n fiierul "pitagora.pl" (unde exist deja predicatul gcd/3) i rencrcm fiierul (la promptul interpretorului tastm "scurttura" [pitagora].). Formulm nti interogri de forma "este adevrat c tripletul cutare este PPT ?":
?- ppt(3, 4, 5). true. % se ndeplinesc toate clauzele PPT ?- ppt(4, 3, 5). false. % Nu satisface a treia clauz ("not" between(3,B,A), unde B=3 i A=4) ?- ppt(30, 40, 50). false. % Nu satisface a patra clauz (avem "not" gcd(30,40, 1))

Pentru un alt exemplu, (20, 99, 101) este PPT - dar ppt(20,99,101) va rspunde "false", pentru c nu este satisfcut prima clauz i ca urmare, nu se mai ajunge la verificarea celorlalte clauze. Dar definiia de mai sus permite i ntrebri de genul "care sunt valorile care satisfac" PPT:
?- ppt(A, 20, C). false. % Nu exist A, C nct A este prim cu 20 i A2 + 202 = C2 ?- ppt(A, 40, C).

A = 9, C = 41 ; false. ?- ppt(X, X = 33, Y = 56 ; X = 16, Y = 63 ; false.

% tripletul (9, 40, 41) este PPT (i tastm ;) % Nu exist o alt soluie (A, 40, C)

Y, 65).
% tripletul (33, 56, 65) este PPT % (16, 63, 65) este PPT % Nu mai sunt soluii (pentru C=65)

Dac vrem o afiare mai convenabil, putem folosi predicatul predefinit writeln; iar dac vrem s form "Redo" (reluarea cutrii pentru o alt soluie, fr s mai tastm ;) atunci putem aduga fail:
... % operm la sfritul definiiei lui ppt: C^2 =:= A^2 + B^2, % am ters . final, nlocuind cu , writeln((A, B, C)), fail.

Rencrcnd apoi "pitagora.pl", vom obine:


?- ppt(X, Y, 65). 33,56,65 16,63,65 false.
fail foreaz backtracking-ul, nct primim deodat toate rspunsurile.

De obicei ns, alta ar fi perspectiva care ne-ar interesa: vrem tripletele "libere" (fr s fixm vreo valoare A, B, sau C) pn la o anumit limit (de exemplu, toate sistemele PPT cu C 1000; sau eventual, numai pe acelea pentru care 1000 C 2000).

Deci, n primul rnd, trebuie s considerm dou argumente Min i Max, ntre valorile crora s varieze "necunoscuta" C. Putem considera pentru Min valoarea implicit 5:
ppt(N) :- ppt(_, _, N, 5). % toate PPT (c,b,a) cu 5 c N
(c > b > a) (c > b > a)

ppt(N, Min) :- ppt(_, _, N, Min). % toate PPT (c,b,a) cu Min c N

ppt(A, B, Max, Min) :between(Min, Max, C), % genereaz o valoare C nct Min C Max between(4, C, B), % 4 B C_curent between(3, B, A), % 3 A B_curent A^2 =:= (C - B) * (C + B), % ceva mai convenabil, dect C^2 =:= A^2 + B^2 gcd(A, B, 1), % A i B sunt coprime writeln((C, B, A)), fail. % afieaz soluia i reia ("Redo")

Dac vrem toate PPT pn la 100: ppt(100) (vezi imaginea alturat). Renunnd la clauza gcd(A,B,1) (o putem "comenta", prefixnd cu %) i repetnd - rencrcm fiierul i lansm ppt(100) vom obine toate cele 52 de triplete pitagoreice pn la 100 (nu numai pe cele 16 care sunt PPT). ns de exemplu pentru ppt(600, 500), rezultatul (lista PPT, ntre 500 i 600) se obine mult mai ncet. Aceasta, pentru c - fa de versiunea "brut" iniial - nu am prevzut dect "optimizri" minore (nelegate de fondul problemei): am nlocuit "C^2 =:= A^2 + B^2" cu o expresie care se evalueaz ceva mai repede i pe de alt parte, am inversat locurile pentru aceast clauz i respectiv, clauza gcd(A,B,1) (ceea ce este totui important: pentru exemplul din imagine, gcd(A,B,1) se va apela acum numai pentru cele 52 de triplete pitagoreice).

Un PPT (a,b,c) are cteva proprieti implicite foarte simple, de care am putea ine seama n vederea eficientizrii programului. a i b fiind coprime, rezult c a, b, c sunt prime dou cte dou i c este impar; deci between(Min, Max, C) ar trebui s genereze doar valorile C impare. Ptratele dau sau restul 0, sau restul 1 la mprirea prin 3; deci, reducnd modulo 3 egalitatea a2 + b2 = c2, avem sau 0+1=1, sau 1+0=1 (cazul 0+0=0 trebuie respins fiindc a,b,c sunt coprime; cazul 1+1=1 trebuie evident, respins). Rezult c sau a, sau b este divizibil cu 3 (iar c nu poate fi multiplu de 3). O analiz similar pentru resturile mpririi prin 4 arat c valorile C trebuie s fie nu numai impare (cum am vzut mai sus), dar chiar congruente cu 1 modulo 4. Analog avem: modulo 16, ptratele sunt 0, 1, 4, sau 9; analiznd cazurile posibile pentru a2 + b2 = c2 (modulo 16) rezult c fie a, fie b este multiplu de 4. Putem evita deocamdat, modificarea "radical" a programului de mai sus (ar trebui rescris
between, nct s genereze numai anumite valori - dar n feluri diferite pentru C, B i respectiv pentru A).

Putem sintetiza cele stabilite mai sus n dou clauze: produsul a*b se divide cu 12 i respectiv, C ia numai valori congruente modulo 4 cu 1:
ppt(A, B, Max, Min) :between(Min, Max, C), 1 =:= C mod 4, between(4, C, B), between(3, B, A), 0 =:= A*B mod 12,
% genereaz o valoare C nct Min <= C <= Max % Dar respinge imediat ("Redo") dac C nu este 1 modulo 4

% "Redo" dac A*B nu se divide cu 12

A*A =:= (C - B) * (C + B), % A*A este mai eficient ca A^2 (sau A**2) gcd(A, B, 1), writeln((C, B, A)), fail. % afieaz soluia i reia ("Redo")

Rencrcnd "pitagora.pl" putem constata c viteza de execuie se mrete de cteva ori, fa de prima versiune. De exemplu, lista PPT pn la 500 rezult cam n 8 secunde; dar mai departe pn la 1000, se ajunge totui la 1 minut:
?- time(ppt(1000)). 5,4,3 13,12,5 . . . . . . 985,864,473 997,925,372
% pentru documentare, ?- help(time).

/* n cazul A^2 =:= (C - B) * (C + B) 52,959,287 inferences, 64.073 CPU in 64.121 seconds (100% CPU, 826543 Lips) */ /* n cazul A*A =:= (C - B) * (C + B) 52,959,130 inferences, 55.258 CPU in 55.295 seconds (100% CPU, 958397 Lips) */

Iniial, am folosit n program operatorul de ridicare la putere, A^2; dar - cum se poate deduce din listingul de mai sus - acesta necesit inferene n plus i timp mai mare dect nmulirea de ntregi (fiindc angajeaz reprezentri i calcul n virgul mobil). Dac ndrznim mai departe ppt(2000, 1000) rezult cam n 6.5 minute (folosind A*A; respectiv, peste 7 minute cnd s-ar folosi A^2). Dar programul poate servi pentru diverse alte obiective (nu numai pentru a lista tripletele PPT ntre

dou limite fixate, ca mai sus). De exemplu: care PPT (A,B,C) cu C < 1000 au A divizibil cu 100: ?- L=[100,200,300,400,500,600,700,800,900], % Lista multiplilor de 100 (< 1000) | member(A, L), % selecteaz n A un membru din lista L | ppt(A, Y, 1000, 100). % PPT (A, Y, C) cu C < 1000 629,621,100 641,609,200 661,589,300 689,561,400 false.

Care triplete PPT ntre 1000 i 2000, au A impar ntre 49 i 63:


?- between(49, 63, A), 1201,1200,49 1301,1300,51 1405,1404,53 1513,1512,55 1625,1624,57 1741,1740,59 1861,1860,61 1985,1984,63 false. 1 =:= A mod 2, ppt(A, Y, 2000, 1000).

Pentru tripletele obinute mai sus avem C - B = 1. Exist i alte asemenea triplete? Putem folosi "ppt" inclusiv n scopul verificrii satisfacerii proprietii PPT prin ?- ppt(45, 1012, 1013, 5). (sau, un exemplu cu A=900: ?- ppt(900,2419,2581, 2581).).

Numrarea soluiilor, n Prolog

Dac ne intereseaz cte soluii sunt i nu lista acestora, n SWI-Prolog putem folosi metapredicatul aggregate(count, Goal, Result): apeleaz clauzele sau predicatul Goal i contorizeaz soluiile, returnnd numrul acestora n variabila "Result". S eliminm n prealabil, ultima linie din programul de mai sus (nu ne mai intereseaz "writeln" deci comentm ntreaga linie i punem . dup clauza gcd(A, B, 1)); rencrcnd apoi "pitagora.pl", putem obine numrul de triplete PPT ntre diverse limite, astfel:
?- aggregate(count, ppt(1000), S). S = 158. ?- aggregate(count, ppt(2000,1000), S). S = 161.

Deci 158 de triplete PPT au ipotenuza mai mic dect 1000 - ceea ce este corect (vezi A101931) i sunt 161 de triplete PPT cu ipotenuza cuprins ntre 1000 i 2000. Obs. Numrul de PPT cu ipotenuza < N este aproximativ N / (2*PI) 0.1591549*N (D. N. Lehmer, 1900) i avem prilejul s verificm calitatea acestei aproximri: pentru N=1000 ea d 159, iar pentru N=2000 d 318 (fa de valorile exacte 158 i respectiv 158+161 = 319). Putem formula i o investigaie mai "amnunit":
?- time((member(N, [100,200,300,400,500,600,700,800,900,1000]), Min is N-100, aggregate(count, ppt(N, Min), S), % PPT cu ipotenuza ntre Min i N writeln((Min-N:S)), fail)). 0-100:16 % 16 PPT cu ipotenuza < 100 100-200:16 % 16 PPT cu ipotenuza ntre 100 i 200

200-300:15 300-400:16 400-500:17 500-600:15 600-700:17 700-800:16 800-900:12 900-1000:18


% % 52,959,340 inferences, 55.136 CPU in 55.187 seconds (100% CPU, 960527 Lips) time/1 cere un singur argument i de aceea, am parantezat setul celor patru clauze care formeaz

interogarea (regul uzual, cnd mai multe clauze ale unui aceluiai scop trebuie vzute ca un singur argument). Dac adunm valorile obinute aici pe intervale de lungime 100 - obinem ntr-adevr, totalul de 158 gsit anterior pentru numrul de PPT pn la 1000. Dar aggregate face parte dintr-un modul specific interpretorului SWI-Prolog. O soluie mai portabil const n folosirea unui predicat ca findall (sau "find_all" n alte interpretoare):
?- findall( (A, B), ppt(A, B, 500, 5), L ), length(L, S). L = [(3,4),(5,12),(8,15),(7,24),(20,21),(12,35),(9,40),(28,45), (..., ...)|...], S = 80.

Orice triplet PPT este unic determinat de catetele sale; findall/3 colecteaz soluiile (A,B) ale ppt(A,B,500,5) n lista L; apoi, length/2 furnizeaz n S lungimea listei L, deci numrul de PPT. Desigur, am parantezat (A, B) nct s constituie un singur argument n "findall"; dar n acest caz, "parantezarea" este n acelai timp un ablon pentru elementele listei (n L avem: (3, 5), etc.) i se putea folosi i vreun alt format (de exemplu, findall(A-B, ...) ducea la L = [ 3-4, 5-12, ...]). De observat n plus, verificarea estimrii lui Lehmer: 0.1591549 * 500 79.58 80.

Mai dm un exemplu (n care avem iari o "parantezare", n scopul unificrii ntr-un singur argument): s gsim PPT n care catetele sunt numere consecutive:
?- findall( A-B, (ppt(A, B, 1000, 5), B-A =:= 1), L ), length(L, S). L = [3-4, 20-21, 119-120, 696-697], S = 4. Aceast metod servete situaia n care este necesar lista soluiilor (ca obiect n contextul execuiei programului, nu doar ca afiare), n scopul prelucrrii n program. Dac vrem doar numrul de soluii, atunci metoda bazat pe "findall" devine artificial (nu este necesar lista soluiilor).

Prin urmare, ajungem la aceast problem: cum se contorizeaz soluiile unui predicat, n Prolog? ntr-un limbaj ca C++ instituim o variabil global iniializat cu 0 i o incrementm dup fiecare soluie obinut. Dar n Prolog variabilele sunt totdeauna "locale" predicatului n care apar i n plus, nu exist "atribuire" (memorare a unei valori ntr-o variabil) ci "unificare"; intern, "ppt(A,B,100,5)" (i orice alt termen) este reprezentat printr-o structur de pointeri (unul pentru functorul "ppt" i cte unul pentru fiecare argument) i "unificarea" revine n fond la validarea faptului c doi pointeri (sau copii locale ale lor) refer o aceeai zon (deci variabilele corespondente "au aceeai valoare"). n Prolog, "program" nseamn declaraii ntr-o "baz de date" i interogri - nct "global" revine la un fapt/regul/predicat care, fie exist din start (predicate predefinite) fie este definit n baza de date indicat interpretorului de ctre utilizator, fie este adugat bazei de date existente, prin efectul curent al execuiei interogrilor programului-utilizator. (este de observat c este vorba de "global", dar nici ntr-un caz de "variabil global"). Se pot aduga declaraii n baza de date existent folosind assertz (sinonim cu assert) i asserta

("la sfrit", respectiv "la nceput"); iar pentru "extragere" i eliminare - retract: ?assert(elev(['Popa', 'Ion'])). true. ?elev(Nume). Nume = ['Popa', 'Ion']. ?retract(elev(X)). X = ['Popa', 'Ion']. ?elev(Nume). false.

n acest exemplu de lucru, prin prima interogare s-a adugat elev(['Popa', 'Ion']) n baza de date; apoi s-a chestionat: exist o valoare pentru "necunoscuta" Nume care s satisfac termenul "elev"?. ntr-o interogare ulterioar, am folosit retract pentru a extrage ntr-o variabil X argumentul termenului "elev" existent i a elimina apoi acest termen din baza de date (interogarea ulterioar "elev(Nume)" nu mai poate fi satisfcut, fiindc faptul respectiv nu mai exist). Un meta-predicat count(Goal, N) care s lanseze repetat un predicat Goal indicat ca argument i s contorizeze de cte ori este satisfcut acesta (altfel spus, s dea numrul N de soluii ale predicatului indicat ca argument) se poate defini folosind assert i retract astfel:
% count(Goal, N) furnizeaz n N numrul soluiilor predicatului Goal

count(Goal, N):assertz(contor(0)), count_(Goal),

% iniializeaz contorul de soluii % lanseaz repetat Goal i actualizeaz contorul /* Urmtoarea clauz se va executa numai dac count_(Goal) d "true" */ retract(contor(N)). % obine n N valoarea final a contorului

count_(Goal):- % Apeleaz repetat Goal, ct timp este satisfcut ("true"). call(Goal), % Cnd Goal d "false", ncheie count_(Goal) cu "false". retract(contor(N)), % Dac Goal este "true": N = rangul soluiei precedente, M is N + 1, % retracteaz contorul curent, incrementeaz N i assertz(contor(M)), % nscrie contorul corespunztor acestei ultime soluii. fail. % Foreaz backtracking la call(Goal), pentru o nou soluie. count_(_).
% ncheie count_(Goal) cu "true" i dup ce s-au epuizat soluiile.

Uneori e mai greu de explicat "ce face" programul, dect s-l scrii - dar ne-am strduit mai sus, s comentm corespunztor clauzele respective. Dac ultima clauz ar lipsi, atunci apelul "count_(Goal)" (a doua linie din "programul principal") s-ar ncheia cu "false" i nu s-ar mai ajunge la clauza "retract(contor(N))"; poate c formularea cu If-Then-Else este mai clar:
% formulare cu If-Then-Else (nu mai necesit clauza final "count_(_).")

count(Goal, N):assertz(contor(0)), % iniializeaz contorul de soluii ( count_(Goal) % va apela recursiv Goal, pn cnd rezult "false" ; % "ELSE" (count_(Goal) nu este satisfcut) - execut "retract"-ul retract(contor(N)) % ). ns n program am preferat (am justificat undeva mai sus, de ce) varianta fr If-Then-Else,

adugnd atunci clauza final count_(_).. ntr-un exemplu redat mai sus am vzut cum putem folosi aggregate pentru a obine numrul de soluii: s-a generat lista soluiilor i s-a afiat lungimea acestei liste. S lansm acelai test, folosind de data aceasta count, pentru a numra direct soluiile:

?- time((

% a doua parantez este necesar fiindc time/1 are un singur argument

member(N,[100,200,300,400,500,600,700,800,900]), Min is N - 100, count(ppt(N, Min), S), writeln(Min-N: S), fail )). 0-100:16 100-200:16 . . . . . . 700-800:16 800-900:12
% 38,584,787 inferences, 39.969 CPU in 40.000 seconds (100% CPU, 965362 Lips) Desigur, constatm c - numrnd direct soluiile prin count, fr a genera lista acestora ca n aggregate - timpul este sensibil mai bun (40 secunde, fa de 55 secunde).

n n jurul unei probleme de echilibru (II) ne-am ocupat de generarea unui anumit tip de partiii ntregi, constituind fiierul "espart2.pl" coninnd predicatele "espart2" i "parts_2". Dac vrem s contorizm soluiile folosind predicatul definit mai sus count, nlocuim linia
Suma_curenta =:= Numar_de_inclus -> scrie([Numar_de_inclus | Part]) din corpul predicatului parts_2" (linie prin care afiam soluia curent), cu:
% Nu mai intereseaz soluiile, ci numrul lor i adugm n fiierul "espart2.pl" urmtorul predicat (angajnd i count):

Suma_curenta =:= Numar_de_inclus -> true

espart3(N, Count) :halfTrig(N, Suma_initiala), count(parts_2(N, [0], Suma_initiala), Count).

ncrcnd pe lng "pitagora.pl" (n care avem definit count) i "espart2.pl" (dup salvarea modificrilor precizate mai sus), putem formula de exemplu, urmtoarea interogare:
?- between(10, 25, N), espart3(N, X), writeln(N: X), fail. 10:40 11:70 12:124 13:221 14:397 15:722 16:1314 17:2410 18:4441 19:8220 20:15272 21:28460 22:53222 23:99820 24:187692 25:353743 false.

Am obinut astfel numrul de "s-partiii" ale segmentului natural [n], pentru n=10..25 (dar vezi
totui n jurul unei probleme de echilibru (I) o soluie mai rapid, angajnd o funcie generatoare

S-ar putea să vă placă și