Sunteți pe pagina 1din 4

Recursivitate n Prolog

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

2.1 Relaii recursive


Limbajul Prolog permite exprimarea definiiilor recursive prin definirea recursiv a predicatelor. Trebuie ntotdeauna s se defineasc un fapt sau o regul care marcheaz punctul de oprire din recursivitate, deci cazul particular al definiiei recursive. Ex1. S se scrie un program care calculeaz n!, unde n! = 1 * 2 * 3 *...* n. % fact(+N, - NFact) fact(0, 1). % Factorialul lui 0 este 1, punctul de oprire. fact(N,NFact) :% Factorialul lui N este NFact daca: N > 0, N1 is N - 1, %calculam (N - 1) pentru a-l putea pasa ca argument fact(N1, Rezult), % fie Rezult factorial de (N - 1) NFact is N * Rezult. % atunci NFact este N inmultit cu factorial de (N - 1) S se scrie un program care calculeaz X la puterea N, unde X i N sunt numere

Ex2. naturale.

% expo(+N, +X, -XlaN) expo( _ , 0, 0). expo(0, X , 1):- X > 0. expo(N, X, Exp) :X > 0, N > 0, N1 is N - 1, expo(N1, X, Z), Exp is Z * X. Ex3. S se scrie un program care calculeaz termenul n din irul lui Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, ..., n care f(n) = f(n - 1) + f(n - 2), pentru n > 2. % fibo(+N, -F) fibo(1, 1). fibo(2, 1). fibo(N, F) :N > 2, N1 is N - 1, N2 is N - 2, fibo(N1, F1), fibo(N2, F2), F is F1 + F2.

Aceast implementare este ineficient, deoarece n calculul lui fibo(N, _ ), predicatul fibo(Nk, _ ) este apelat de k ori. Implementarea urmtoare elimin aceast deficien, deoarece predicatul fib, care calculeaz F = f(M), este apelat cu ultimii doi termeni F1 = f(M-2) i F2 = f(M-1), pentru M = 2 N: fibo1(N, F) :- fib(2, N, 1, 1, F). % predicat auxiliar fib(+M, +N, +F1, +F2, -F) fib(M, N, _ , F2, F2) :- M >= N. fib(M, N, F1, F2, F) :M < N, M1 is M + 1, F1plusF2 is F1 + F2, fib(M1, N, F2, F1plusF2, F). Ex4. S se scrie un predicat care calculeaz cel mai mare divizor comun pentru dou numere. Se folosete definiia recursiv a lui Euclid: Fie a i b dou numere naturale. dac b = 0 atunci cmmdc(a, b) = a; altfel cmmdc(a, b) = cmmdc(b, r), unde r este restul mpririi lui a la b. Implementarea n Prolog este: % cmmdc(+A, +B, -Cmmdc) cmmdc(A, 0, A). cmmdc(A, B, Rez) :- B > 0, Rest is A mod B, cmmdc(B, Rest, Rez). Ex5. S scriem predicatul divizor care testeaz dac un numr A divide un numr B. Pe baza acestui predicat s se scrie predicatul prim care verific dac un numr este prim. % divizor(+A,+B) % prim(+N) divizor(A, B) :- 0 is B mod A. prim(N):- N > 1, Div is N - 1, nu_are_div(N, Div). nu_are_div(_, 1). nu_are_div(N, Divizor) :not(divizor(Divizor, N)), DivNou is Divizor - 1, nu_are_div(N, DivNou). Predicatul rezolv(N) rezolv problema turnurilor din Hanoi cu N discuri. % rezolva(+N) rezolva(N) :- hanoi(N, stnga, dreapta, mijloc). hanoi(0, _ , _ , _ ). hanoi(N, A, B, C) :N > 0, N1 is N - 1, hanoi(N1, A, C, B), write('Din '), write(A), write(' pe '), write(B), nl, hanoi(N1, C, B, A).

Ex6.

2.2

Direcia de construire a soluiilor

Majoritatea programelor Prolog se bazeaz pe recursivitate. Ca n orice limbaj de programare recursiv, parametrii de ieire ai subprogramelor, deci argumentele sintetizate ale predicatelor n cazul limbajului Prolog, pot fi calculate fie pe ramura de avans n recursivitate, fie pe ramura de revenire din recursivitate. Se consider dou variante de definire a predicatului de numrare a elementelor dintr-o list. Prima variant, nr_elem(Lista, NrElem), construiete soluia (calculeaz numrul de elemente din list) pe ramura de revenire din recursivitate. % nr_elem(Lista, NrElem) nr_elem([], 0). nr_elem([ _ | Rest], N) :- nr_elem(Rest, N1), N is N1 + 1. A doua variant, nr_elem2(Lista, NrElem) calculeaz numrul de elemente din list pe ramura de avans n recursivitate. Pentru a realiza acest lucru, se folosete un predicat ajuttor nr_elem1(Lista, NrAcumulat, NrElem), care are un argument suplimentar fa de predicatul nr_elem2. Acest argument, NrAcumulat, are rolul de variabil de acumulare a numrului de elemente din list, pe msura avansului n recursivitate i este instaniat la primul apel la valoarea 0. n momentul atingerii punctului de oprire din recursivitate, deci n cazul n care lista ajunge vid, valoarea acumulat n argumentul NrAcumulat este copiat n cel de-al treilea parametru al predicatului nr_elem1. Se face apoi revenirea din apelurile recursive succesive, fr a efectua nici o alt prelucrare, predicatul nr_elem1 reuete i trimite valoarea calculat n NrElem predicatului iniial nr_elem2. % nr_elem2(Lista, NrElem) % nr_elem1(Lista, NrAcumulat, NrElem) nr_elem2(Lista, N) :- nr_elem1(Lista, 0, N). nr_elem1([], N, N). nr_elem1([ _ | Rest], N1, N2) :- N is N1 + 1, nr_elem1(Rest, N, N2). O abordare similar se poate vedea n cazul celor dou variante de definire a predicatului de intersecie a dou liste (determinarea elementelor comune celor dou liste). Prima variant, inter(Lista1, Lista2, ListaRez), calculeaz soluia (lista intersecie) pe ramura de revenire din recursivitate. Cea de a doua variant, int(Lista1, Lista2, ListaRez), calculeaz soluia pe ramura de avans n recursivitate, utiliznd int1(Lista1, Lista2, ListaAcumulare, ListaRez) ca predicat ajuttor cu parametrul suplimentar ListaAcumulare, instaniat la primul apel la lista vid. % inter(Lista1, Lista2, ListaRez) membru(Elem, [Elem|_]). membru(Elem, [_|Rest]) :- membru(Elem, Rest). inter([], _, []). inter([Prim|Rest], Lista2, [Prim|LRez]) :membru(Prim, Lista2), !, inter(Rest, Lista2, LRez). inter([ _ | Rest], Lista2, LRez) :- inter(Rest, Lista2, LRez).

% int(Lista1, Lista2, ListaRez) % int1(Lista1, Lista2, ListaAcumulare, ListaRez) int(L1, L2, LRez) :- int1(L1, L2, [], LRez). int1([], _, L, L). int1([Prim|Rest], L, L1, L2) :membru(Prim,L), !, int1(Rest, L, [Prim | L1], L2). int1([ _ | Rest], L, L1, L2) :- int1(Rest, L, L1, L2). Aceast tehnic de programare, des ntlnit n programele Prolog, are o serie de avantaje, printre care o claritate crescut i, n principal, utilizarea definiiilor de predicate recursive la urm. Un predicat recursiv la urm este un predicat n care apelul recursiv al acestuia este ultimul scop din conjuncia de scopuri care l definete i pentru care nu mai exist nici o regul posibil de aplicat dup apelul recursiv. Un astfel de predicat are avantajul c poate fi apelat recursiv de oricte ori, fr a genera o depire a stivei. n plus, execuia unui astfel de predicat este mai eficient. Pentru a pune n eviden aceast comportare se definesc patru variante ale unui predicat care afieaz (numr) valori ntregi succesive la infinit, ncepnd de la o valoare fixat N. % Predicatele numara(N), numara1(N), rau_numara(N) si % rau_numara1(N) afiseaza intregi succesivi pornind de la valoarea N. numara(N) :- write(N), nl, N1 is N + 1, numara(N1). numara1(N) :- N >= 0, !, write(N), nl, N1 is N + 1, numara1(N1). numara1( _ ) :- write('Valoare negativa.'). rau_numara(N) :- write(N), nl, N1 is N + 1, rau_numara(N1), nl. rau_numara1(N) :- N >= 0, write(N), nl, N1 is N + 1, rau_numara1(N1). rau_numara1(N) :- N < 0, write('Valoare negativa.'). Primele dou variante ale acestui predicat, numara(N) i numara1(N), sunt predicate recursive la urm. Predicatul numara(N) este definit printr-o singur regul i apelul recursiv este ultimul scop al definiiei. Predicatul numara1(N) este definit prin dou reguli, dar, datorit predicatului cut din prima regul, dac N 0 nu mai exist nici o alt regul posibil de aplicat n momentul apelului recursiv. Ambele variante vor numra la infinit, ncepnd de la N i afind valori succesive. Urmtoarele dou variante, rau_numara(N) i rau_numara1(N), nu sunt predicate recursive la urm. Predicatul rau_numara(N) nu are apelul recursiv ca ultim scop n definiie, iar rau_numara1(N) are o variant (regula) nencercat n momentul apelului recursiv. Execuia ambelor predicate se va opri dup un anumit numr de valori, cu afiarea unui mesaj de eroare cauzat de depirea stivei.