Sunteți pe pagina 1din 6

Vitalie Cotelea

13
3
Listele i tratarea lor
3.1. Reprezentarea i unificarea
listelor
n Prolog lista este reprezentat printr-un termen
structurat standard, predefinit, a crui functor este caracterul .
i are dou componente: primul element al listei i restul listei.
Lista vid este reprezentat prin atomul special []. De exemplu,
o list cu un singur element a se reprezint n Prolog .(a,[]), iar o
list cu trei elemente a,b,c se reprezint .(a,.(b,.(c,[]))).
Ca i orice termen structurat, o list poate fi reprezentat
n form de arbore. De exemplu, listele .(a,[]) i .(a,.(b,.(c,[])))
pot fi reprezentate ca n Fig.3.1 i Fig. 3.2, corespunztor.
.
a [ ]

Fig. 3.1. O list cu un singur element a
Menionm, c ntruct ordinea elementelor n list este
relevant, lista .(a,.(b,.(c,[]))) este diferit de lista .(c,.(b,.(a,[]))).
Deoarece functorul . este definit i ca un operator infix, el
poate fi utilizat n locul formei prefix. De exemplu, (a.[]) i
(a.(b.(c.[]))). Parantezele pot fi omise, fiindc . este definit ca
un operator asociativ n dreapta. Astfel, listele de mai sus au
forma a.[] i a.b.c.[].
.
a
[ ]
.
.
b
c

Fig. 3.2. O list ce conine atomii a,b,c
Dat fiind faptul, c lista e o structur intens utilizat,
Prologul ofer i o notaie foarte comod. Elementele listei se iau
n paranteze ptrate i se separ prin virgul. Aadar, lista ce
conine un singur atom a se scrie [a], iar lista compus din
atomii a, b i c se scrie [a,b,c]. Elemente ale listei pot fi atomi,
numere, liste i, n general, orice structuri. Listele, bineneles,
pot conine i variabile. Urmtoarele liste sunt, de exemplu,
corecte:
[a, sosit, [ziua, de, mine]]
[a, X, b, [Y, c]]
[cartea(cugetari,autor(blaise,pascal)),
cartea(moby_dick,autor(herman,melville))]
Presupunem, c e necesar de specificat o list cu primele
dou elemente cunoscute i un numr nedefinit de alte elemente.
O astfel de list poate fi reprezentat, utiliznd simbolul lexical
"|". El separ elementele ce constituie capul listei de o variabil
ce reprezint restul sau coada listei. Un exemplu poate fi lista
[unu, doi|X]. Deci, "|" divizeaz lista n dou pri: capul listei
o enumerare de elemente naintea lui "|" (elementele unu, doi) i
restul listei (variabila X). Capul listei este ntotdeauna
reprezentat explicit, iar coada poate fi reprezentat implicit
printr-o variabil n dreapta simbolului "|".

Lista Cap Coada
[a,b,c] a [b,c]
[a,b] a [b]
[a] a [ ]
[ ]
[[a,b],c] [a,b] [c]
[a,[b,c]] a [[b,c]]
[a,[b,c],d] a [[b,c],d]
[[1,2,3],[2,3,4],[ ]] [1,2,3] [[2,3,4],[ ]]
Fig. 3.3. Exemple de separare a capului de coad
Astfel, bara vertical | servete pentru separarea
capului listei de coad. Virgula se folosete pentru separarea
unui element de elementul urmtor. n notaia [unu|X] capul,
unu, este un element (aici primul din list), pe cnd coada, X,
este restul listei, adic o alt list mai scurt cu un element. De
exemplu, se poate scrie [unu|[doi,trei]]. Dar nu se poate scrie
[unu|doi,trei], fiindc ceea ce urmeaz dup bar nu este o list.
naintea barei suntem obligai s enumerm explicit toate
elementele capului. De exemplu, n Fig. 3.3 sunt evideniate
notaia listei, capul i coada listei.

List Semnificaie
[X|L] Toate listele nevide
[ X, Y, Z |[ ]] Toate listele cu trei elemente
[X,Y,Z,Coada] Toate listele cu patru elemente
[ X, X, Coada] Toate listele cu trei elemente n care primul i
al doilea elemente reprezint acelai termen
[41,X|Y] Toate listele cu cel puin dou elemente
primul din care este 41
[a,_,_,b] Toate listele cu patru elemente n care primul
i al patrulea sunt atomii a i b, iar celelalte
pot fi orice termen
Fig. 3.4. Liste cu variabile i semnificaiile lor
n afar de aceasta, o list poate fi reprezentat n mai
multe feluri. De exemplu, notaiile ce urmeaz sunt echivalente:
[1,2,3], [1, 2,3| [ ]], [1,2|[3|[ ]]], [1|[2,3|[ ]]], [1|[2|[3|[ ]]]]
iar n Fig. 3.4 este ilustrat semnificaia listelor ce conin
variabile.
Vitalie Cotelea
14
Lista 1 Lista 2 Instanieri
[X, Y, Z] [a, b, c] X=a Y=b Z=c
[a] [X|Y] X=a Y=[]
[X|Y] [a, b, c] X=a Y=[b,c]
[X,Y] [a, b, c] no
[X, Y|Z] [a, b, c] X=a Y=b Z=[c]
[X|[Y|Z]] [a, b, c] X=a Y=b Z=[c]
[X|Y] [[1,2], a,b] X=[1,2] Y=[a,b]
[[a, Y]|Z] [[X, b],
[c, d]]
X=a Y=b Z=[[c, d]]
[X|Y] [] no
[X|Y] [X|[Y]] no
[X|Y] [[1,2]|X] X=Y;
X=[1,2]

Fig. 3.5. Exemple de unificare a listelor
Considerm cteva exemple (Fig. 3.5) pentru a vedea
cum dou liste (ce conin variabile) se unific. Aici se utilizeaz
cunoscutele reguli de unificare a termenilor.
3.2. Tratarea listelor
Unul din motivele utilizrii listelor rezid n capacitatea
lor de a exprima situaii dinamice. Algoritmii de prelucrare, ns,
trebuie s fie explicii. Aceast cerin este remediat prin
metode inteligente.
Majoritatea predicatelor asupra listei divizeaz lista n
cap i coad. Asupra capului se aplic o operaie, apoi nsi
coada se mparte n cap i coad i operaia e aplicat din nou.
Procesul continu pn cnd nu se ntlnete o condiie de
terminare. Cea mai evident condiie este lista vid. Astfel, dac
avem lista [unu,doi,trei], ea poate fi redus n patru pai:
1. Cap=unu, Coada=[doi,trei]
2. Cap=doi, Coada=[trei]
3. Cap=trei, Coada=[]
4. Lista vid ([])
Prelucrarea acestei liste poate fi reprezentat ca n Fig. 3.6
[unu,doi,trei]
[doi,trei]
[trei]
[trei]
[doi]
[unu]
[ ]
[ ]

Fig. 3.6. Prelucrarea unei liste
Forma arborelui din Fig. 4.6 ne sugereaz, c procedura
de prelucrare a unei liste foarte mult seamn cu programul de
construire a unei ci din oraul A n oraul B. Programul coninea
dou reguli, dintre care prima era condiia de terminare, iar a
doua clauza recursiv:
cale(OrasA, OrasB):-
ruta(OrasA, OrasB).
cale(OrasA, OrasB):-
ruta(OrasA, OrasC),
cale(OrasC, OrasB).
Dup acelai model vom descrie, de exemplu, un
predicat de afiare a unei liste. Prima clauz va avea forma
afisare_lista([]).
A doua clauz este o regul recursiv, ce are forma
afisare_lista([Cap|Coada]):-
write(Cap),nl,
afisare_lista(Coada).
Punnd ambele mpreun, obinem
afisare_lista([]).
afisare_lista([Cap|Coada]):-
nl,write(Cap),
afisare_lista(Coada).
Predicatul afisare_lista/1 are un singur argument (o list)
i el afieaz elementele listei. Iar write/1 e un predicat predefinit
ce afieaz argumentul su pe un dispozitiv standard. Predicatul
nl/0 este un predicat predefinit de trecere la un rnd nou.
Predicatul afisare_inver/1 e aproape identic cu
afisare_lista/1, numai c el imprim elementele listei n ordine
invers. Aceasta se face simplu, schimbnd ordinea
subscopurilor n regula recursiv.
afisare_inver([]).
afisare_inver([Cap|Coada]):-
afisare_inver(Coada),
nl, write(Cap).
Procesul de mai sus este destul de standard i
programatorul nceptor l poate urma pentru a scrie definiii
recursive ale predicatelor de manipulare a listelor. Menionm,
c sunt i excepii care scap de principiul general de recursie,
dar acestea vor fi considerate n seciunile urmtoare.
Criteriile de terminare sunt eseniale n prelucrarea
listelor, fiindc ele reprezint condiiile fr care sistemul
eueaz. Se cunosc trei criterii principale de terminare:
terminarea cnd lista este vid, cnd elementul specificat e gsit
i cnd elul specificat este atins.
3.3. Terminarea prelucrrii cnd
lista e vid
Un astfel de exemplu deja a fost descris de predicatul
afisare_lista/2. Punctul cheie const n faptul c procesul ia
sfrit cnd este atins lista vid - [].
3.3.1. Predicatul este_lista/1
Predicatul este_lista/1 este unul din cele mai simple
predicate ce face parte din aceast familie. Idea const n aceea,
c un argument este o list, dac el poate fi redus la lista vid.
este_lista([]).
este_lista([_|Coada]):-
este_lista(Coada).
S observm, c nu are loc nici un proces, att n prima
clauz, ct i n a doua. n afar de aceasta, este utilizat
variabila anonim, fiindc asupra capului nu se aplic
procesul_2/1 i deci, nu e nevoie s cunoatem capul. Aici sunt
interesante urmtoarele ntrebri:
?-este_lista([])
yes
?-este_lista([a,b,c])
yes
Vitalie Cotelea
15
3.3.2. Calcularea lungimii unei liste
O diferen evident dintre predicatul precedent i cel de
calculare a lungimii unei liste, lungime/2, este c ultimul are
dou argumente. Pentru cei ce utilizeaz alte limbaje de
programare, care includ proceduri-funcii, acest predicat poate
trezi nedumeriri. Aici predicatul nu ntoarce valoarea ca n cazul
funciilor din limbajele imperative. Valoarea pe care dorim s-o
obinem se include n calitate de argument. Predicatul de mai jos
are un singur el - calcularea numrului de elemente ntr-o list
dat. S observm, c acest predicat puin se deosebete de
modelul de baz.
lungime([],Total):-
Total=0.
lungime([_|Coada],Total):-
lungime(Coada,Cont),
Total=Cont+1.
Principala diferen este c ordinea subscopurilor
recursiv i de proces din regula recursiv sunt schimbate cu
locurile.
3.3.3. Concatenarea a dou liste
Vom scrie un predicat de concatenare a dou liste. l
vom numi concatenare/3 i va avea trei argumente, toate liste.
Acest predicat este foarte utilizat. Dac avem listele [1,2,3] i
[4,5] putem s obinem lista [1,2,3,4,5].
Procesul de concatenare a unei liste cu alta va decurge,
alipindu-se cte un element de la una la cealalt. S considerm
mai nti condiia de terminare. E evident, c dac prima list
este vid, atunci rezultatul concatenrii va fi lista a doua.
concatenare([],L2,L3):-
L2=L3.
Programatorii mai experimentai las o mare parte de
lucru pe seama unificrii argumentelor, micornd astfel
numrul de subscopuri. n acest exemplu se pot unifica
argumentele L2 i L3, scriind acelai nume. Atunci regula de
mai sus se transform ntr-un fapt.
concatenare([],L,L).
Pn a trece la regula recursiv, s vedem rspunsurile la
nite ntrebri referitor la faptul de mai sus.
?-concatenare([],[4,5],[4,5])
yes
?-concatenare([],[4,5],L)
L=[4,5]
?-concatenare(Var,[4,5],L)
Var=[],L=[4,5]
?-concatenare([],[],L)
L=[]
?-concatenare(Var,[],L)
Var=[],L=[]
Primele dou rspunsuri sunt clare. n cazul trei, primul
argument al ntrebrii este o variabil neinstaniat care se
unific cu []. Ultimele dou ntrebri sunt mai interesante. Din
ele se vede c faptul nostru are dreptul la existen pentru orice
valoare a argumentului doi, fie o list vid sau o list nevid.
Acum se poate scrie regula recursiv. Ea trebuie s
lucreze cu primul argument. O definiie a regulii recursive, ce se
potrivete modelului de baz, poate fi
concatenare([C1|Cd1],L2,[C3|Cd3]):-
C1=C3,
concatenare(Cd1,L2,Cd3).
Aceast regul poate fi citit: O list este adevrat, dac este
adevrat c se poate unifica capul primei liste cu capul listei trei
i se poate alipi coada primei liste la lista a doua, obinnd coada
listei trei.
Ca i n cazul condiiei de terminare, se poate prezenta
aceast regul mai succint, doar renumind variabilele ce exprim
capetele listelor unu i trei cu acelai nume.
concatenare([C|Cd1],L2,[C|Cd3]):-
concatenare(Cd1,L2,Cd3).
Grupnd aceste dou clauze mpreun, se obine definiia
predicatului de concatenare a dou liste.
concatenare([],L,L).
concatenare([C|Cd1],L2,[C|Cd3]):-
concatenare(Cd1,L2,Cd3).
S considerm cteva ntrebri referitor la acest predicat.
?-concatenare([1,2,3],[4,5],L3)
L3=[1,2,3,4,5]
?-concatenare([1,2,3],L2,[1,2,3,4,5])
L2=[4,5]
?-concatenare(L1,[4,5],[1,2,3,4,5])
L1=[1,2,3]
Interesant este ntrebarea Care sunt perechile de liste,
concatenarea crora produc lista [1,2,3]?:
?-concatenare(L1,L2,[1,2,3])
L1=[], L2=[1,2,3];
L1=[1], L2=[2,3];
L1=[1,2], L2=[3];
L1=[1,2,3], L2=[]
Deci, n cazul cnd un singur argument n predicatul de
concatenare este variabil, exist o singur soluie, iar pentru
cazul n care primele dou argumente sunt variabile se obin mai
multe soluii corespunztoare primelor dou liste care prin
concatenare formeaz cea de a treia list.
3.4. Terminarea prelucrrii cnd
elementul specificat e gsit
n calitate de exemplu pot fi considerate predicatele de
cutare a unui element ntr-o list sau de aplicare a unei operaii
asupra unui element, cum ar fi substituirea cu un alt element.
Diferena dintre acest model i precedentul este c n
cazul terminrii cu list vid se examineaz orice element (fie se
numr sau fie se alipete la o list). n cazul de fa, ne
intereseaz doar elementul pe care l cutm, celelalte fiind
irelevante. Ca i n cazul precedent avem de afacere doar cu un
model.
3.4.1. Calitatea de membru al listei
Considerm urmtoarea problem: exist o list de
termeni Prolog i un termen despre care dorim s tim dac este
sau nu n aceast list. Ce metod trebuie de utilizat pentru a
soluiona aceast problem?
Predicatul ce definete aceast problem este utilizat n
multe aplicaii, fiindc el testeaz apartenena unui element la o
list. Idea de realizare const n urmtoarele. Mai nti de toate,
Vitalie Cotelea
16
se verific dac termenul cutat nu este nsi capul listei. Dac
da, atunci el e gsit. n caz contrar, se verific dac termenul nu
se gsete n coada listei. Dac s-a atins sfritul listei, atunci
termenul nu aparine ei.
S vedem acum cum se scrie n Prolog predicatul
respectiv. Se definete predicatul apartine(Elem, Lista), care e
adevrat, dac termenul desemnat de Elem este membru al listei
desemnate de Lista. Se vede, mai nti, cum se verific dac
Elem este capul listei Lista. Acest lucru este exprimat de faptul
apartine(Elem,[Elem|_]).
ntr-a doilea rnd, se definete c Elem este membru al unei liste,
dac el se gsete n restul listei. Regula
apartine(Elem,[_|Rest]):-
apartine(Elem,Rest).
exprim exact acest fapt: Elem aparine listei, dac Rest este
coada listei i Elem este n Rest.
Notm, c lucrul, pe de o parte, l face unificarea i
anume extragerea capului listei - n prima clauz i extragerea
cozii listei - ntr-a doua clauz, pe de alt parte, lucrul l face
variabila anonim. Aici variabila anonim se unific cu partea
listei care nu se consider (coada n prima clauz i capul n
clauza a doua).
Se observ i schema general a unui program recursiv.
n primul rnd, este prezent cazul terminal exprimat de prima
clauz. In al doilea rnd, cazul general cu un apel recursiv
exprimat de clauza secund. Mai exist, de fapt, un caz terminal
suplimentar, implicit, atunci cnd lista n care se caut elementul
este vid. Aici cutarea eueaz, fiindc argumentul al doilea din
capetele ambelor clauze nu se pot nici cum unifica cu lista vid.
Acum, s formulm cteva ntrebri, coninnd acest
predicat.
?-apartine(c,[a,b,c,d])
yes
?-apartine(e,[a,b,c,d])
no
?-apartine(X,[a,b,c,d])
X=a;
X=b;
X=c;
X=d
Pentru cazul n care primul argument este o variabil,
exist patru soluii posibile ale scopului. Acest predicat pune n
eviden o facilitate deosebit de interesant a limbajului Prolog -
puterea lui generativ. Predicatul apartine/2 poate fi folosit att
pentru testarea apartenenei unui element la o list, ct i pentru
generarea pe rnd a elementelor unei liste prin resatisfacerea
succesiv. n anumite contexte de utilizare aceast facilitate
poate fi folositoare, iar n alte contexte ea poate genera efecte
nedorite.
S menionm, c unii programatori neexperimentai
deseori mai adaug o clauz la cele dou, care de fapt este
incorect:
apartine(_,[]).
Fiind adugat aceast clauz la celelalte dou,
urmtoarea ntrebare are rspuns afirmativ.
?-apartine(unu,[]).
yes
Dac acesta este rspunsul ateptat, atunci e bine. Dar
majoritatea consider c dac un obiect nu este n lista
examinat, atunci predicatul trebuie s eueze. Deci, pentru a
anuna eecul nu trebuie de adugat nimic la program. Prologul
face aceasta singur.
De remarcat, c predicatul apartine/2 este inclus n
bibliotecile multor versiuni ale limbajului Prolog.
3.4.2. Eliminarea unui element din list
n urmtorul exemplu vom elimina un element dintr-o
list. Acest predicat e similar predicatului apartine/2, dar mai are
i al treilea argument pentru a reprezenta lista fr elementul
specificat.
eliminare_elem(E,[E|Cd],Cd).
eliminare_elem(E,[C|Cd1],[C|Cd2]):-
eliminare_elem(E,Cd1,Cd2).
S examinm mai nti condiia de terminare:
eliminare_elem(E,[E|Cd],Cd).
Ea este adevrat, dac elementul specificat, E, este capul listei.
n acest caz, al treilea argument este coada listei.
Regula recursiv seamn mult cu cea a predicatului
concatinare/3.
eliminare_elem(E,[C|Cd1],[C|Cd2]):-
eliminare_elem(E,Cd1,Cd2).
n aceast regul C al primei liste se unific cu C al listei doi,
care, de fapt, este prima list fr elementul specificat.
Subscopul din corpul regulii specific c E este n Cd1, iar Cd2
este lista cutat fr E.
Sunt interesante rspunsurile la urmtoarele ntrebri
referitor la predicatul eliminare_elem/3.
?-eliminare_elem(3,[1,2,3,4],Rez)
Rez=[1,2,4]
?-eliminare_elem(1,[1],Rez)
Rez=[]
?-eliminare_elem(4,[1,2,3],Rez)
no
?-eliminare_elem(1,[],Rez)
no
?-eliminare_elem(2,[1,2,3,2],Rez)
Rez=[1,3,2];
Rez=[1,2,3]
Acest program se poate utiliza pentru a gsi toate listele
ce pot fi create din lista dat, adugnd la ea un element:
?-eliminare_elem(2,Lista,[1,3])
Lista=[2,1,3];
Lista=[1,2,3];
Lista=[1,3,2]
De asemenea, se pot afia toate perechile dintr-un
element i o sublist, formate dintr-o list dat:
?-eliminare_elem(E,[1,2,3],Lista)
E=1, Lista=[2,3];
E=2, Lista=[1,3];
E=3, Lista=[1,2]
Vitalie Cotelea
17
Este evident c predicatul eliminare_elem/3 poate avea
diverse utilizri. Una din ele este inserarea unui element ntr-o
list. Astfel, un sinonim pentru eliminare_elem/3 poate fi definit
inserare(E,Lista,Rez):-
eliminare_elem(E,Rez,Lista).
3.5. Terminarea prelucrrii cnd
elul specificat este atins
n acest caz listele se prelucreaz pn nu se ntlnete
un punct specificat dup care se purcede la efectuarea unor
operaii.
3.5.1. Gsirea elementului fiind dat
poziia lui n list
Predicatul pozitie/3 reprezint exact modelul de mai sus.
Idea const n obinerea elementului concret cnd contorul se
reduce la 1. Acest predicat gsete elementul, dac este dat
poziia sa n list.
pozitie(1,E,[E|_]).
pozitie(Cnt,E,[_|Coada]):-
Cnt1=Cnt-1,
pozitie(Cnt1,E,Coada).
Dac sunt formulate ntrebrile ce urmeaz, se obin
rspunsurile respective.
?-pozitie(0,E,[1,2,3]).
no
?-pozitie(2,E,[1,2,3]).
E=2
?-pozitie(4,E,[1,2,3]).
no
Aici trebuie de constatat c nu se pot formula ntrebri n
care se indic elementul i lista, iar n rezultat s se cear
obinerea poziiei elementului n lista dat.
3.5.2. Eliminarea unui element indicat
de poziia sa n list
Acest predicat este adevrat, dac se obine o list din
lista dat, dar fr elementul sub numrul de ordine n. Ca i n
predicatul pozitie/3, n calitate de argument apare valoarea care
indic numrul elementului n list ce trebuie eliminat.
eliminare_n(1,[_|Cd],Cd).
eliminare_n(Cnt,[C|Cd1],[C|Cd2]):-
Cnt1=Cnt-1,
eliminare_n(Cnt1,Cd1,Cd2).
Notm, c n clauza recursiv capul primei liste s-a
unificat cu capul listei doi. S formulm, acum, nite ntrebri.
?-eliminare_n(0,[1,2,3],L).
no
?-eliminare_n(2,[1,2,3],L).
L=[1,3]
?-eliminare_n(4,[1,2,3],L).
no
3.6. Acumulatoarele
n aceast seciune se redefinesc unele predicate din
seciunile de mai sus cu scopul de ale face mai eficiente. Pentru
aceasta se folosesc acumulatoarele. Acumulatorul este un
argument adiional pentru fixarea datelor intermediare.
3.6.1. Calcularea lungimii unei liste
S reproducem aici predicatul de calculare a numrului
de elemente ntr-o list, considerat mai sus
lungime([],0).
lungime([_|Coada],Total):-
lungime(Coada,Cont),
Total=Cont+1.
El utilizeaz o variabil numit Cont, care joac rolul de contor.
n acest predicat, pentru a susine calcularea, se introduce un
acumulator reprezentat de un alt argument. Atunci se scrie
urmtoarea condiie nou de terminare.
lungime_ac([],Total,Total).
Aceast condiie ne spune c dac lista e vid, atunci celelalte
argumente au aceeai valoare.
Condiia recursiv este o rearanjare a versiunii vechi.
lungime_ac([_|Coada],Suma,Total):-
Cont=Suma+1,
lungime_ac(Coada,Cont,Total).
La fiece pas al recursiei regula adaug cte o unitate la suma
acumulat. S descifrm cum Cont crete i cum variabila Total
este instaniat atunci cnd este atins condiia terminal.
Iat unele rspunsuri la ntrebri referite la acest
predicat.
?-lungime_ac([1,2,3],0,Total)
Total=3
?-lungime_ac([1,2,3],0,4)
no
?-lungime_ac([],0,Total)
Total=0
?-lungime_ac(Lista,0,3)
Lista=[_,_,_]
Este incomod de a atribui 0 argumentului doi ori de cte
ori se formuleaz ntrebarea. De aceea se mai scrie o regul
adiional care apeleaz la regula ce conine 0 n calitate de
argumentul doi.
lungime_ac(Lista,Total):-
lungime_ac(Lista,0,Total).
3.6.2. Indexarea listei
Deja s-a menionat c predicatul pozitie/3 nu poate afia
poziia n list a unui element dat. Vom rescrie acest predicat
pentru ca el s soluioneze ambele probleme: gsirea poziiei
elementului n list dac el e specificat i gsirea elementului n
list dac este dat poziia lui.
Vitalie Cotelea
18
pozitie_ac(Poz,Poz,E,[E|_]).
pozitie_ac(Cnt,Poz,E,[_|Cd]):-
Cnt1=Cnt+1,
pozitie_ac(Cnt1,Poz,E,Cd).
Asemenea predicatului lungime_ac/3, predicatul
pozitie_ac/4 utilizeaz un contor (numit Cnt1 i Cnt) i un total
(numit Poz) pentru a pstra calea spre poziia elementului.
Condiia recursiv este adevrat, cnd contorul se mrete cu o
unitate i apelul recursiv este adevrat. Condiia de terminare
este adevrat, dac capul listei se unific cu E i Cnt poate fi
unificat cu Poz.
Din nou se poate formula un apel mai simplu al regulii:
pozitie_ac(Poz,E,Lista):-
pozitie_ac(1,Poz,E,Lista).
ntrebrile de mai jos ilustreaz unele utilizri ale
predicatului.
?-pozitie_ac(2,E,[1,2,3])
E=2
?-pozitie_ac(Poz,2,[1,2,3])
Poz=2
3.6.3. Utilizarea eficient a recursiei
Ar fi incorect s se cread c numai utilizarea
acumulatoarelor face ca predicatele s fie bidirecionale ca cel
de mai sus. Recursia, eventual, este un mare consumator de
memorie operativ, deoarece ea poate necesita memorarea
tuturor calculelor pentru toate adncimile recursiei. n
programele Prolog efectiv mari, recursia este frecvent utilizat i
deci, i consumul de memorie poate fi imens. Dar exist tehnici
de implementare a recursiei cu un consum de memorie foarte
economicos. S examinm dou versiuni ale unei reguli
recursive.
lungime([_|Cd],Total):-
lungime(Cd,Cont),
Total=Cont+1.
lungime_ac([_|Cd],Suma,Total):-
Cont=Suma+1,
lungime_ac(Cd,Cont,Total).
n prima versiune, primul subscop (lungime(Cd,Cont))
este nondeterminist, fiindc pot fi mai multe alternative de
potrivire (unificare) a clauzei n programul Prolog. Al doilea
subscop este determinist, fiindc exist o singur soluie pentru
Total=Cont+1.
n a doua versiune, primul subscop (Cont=Suma+1) este
determinist, iar al doilea (lungime_ac(Cd,Cont,Total)) -
nondeterminist.
n Prolog utilizarea eficient a memoriei se bazeaz pe
determinism i nondeterminism. S mai considerm odat
versiunea doi. ntruct primul subscop (Cont=Suma+1) este
determinist, atunci cnd al doilea subscop
(lungime_ac(Cd,Cont,Total)) este atins, Prologul nu trebuie s
memoreze nimic despre subscopul precedent. Aceasta nseamn,
c chemarea recursiv poate utiliza memoria ce a fost ocupat de
chemarea curent. Deci, nu este nici o cretere a memoriei cu
adncirea recursiei.
n cazul primei versiuni, primul subscop este
nondeterminist i atunci Prologul nu poate suprascrie n memoria
utilizat de chemarea curent a recursiei. Deci, are loc un
consum suplimentar de memorie.
Aceast tehnic de economisire a memoriei n procesul
recursiei este o parte a tehnicii, numit optimizarea prin recursia
terminal (Tail Recursion Optimisation). Numele se trage de la
poziia n corpul regulii a subscopului recursiv (este ultimul sau
nu). n opoziie cu recursia terminal se gsete recursia n
stnga (vezi prima versiune). Programatorii experimentai se
identific prin aplicarea larg a cozii recursive.
3.6.4. Inversarea eficient a listei
Vom examina un predicat de inversare a ordinii
elementelor n list, cunoscut sub numele de inversare naiv.
Acest predicat este deseori folosit ca etalon de msurare
comparativ a vitezei interpretatoarelor i compilatoarelor
Prolog. Inversarea naiv utilizeaz predicatul concatenare/3.
O definiie poate fi
inversare_naiva([],[]).
inversare_naiva([C|Cd],Inv):-
inversare_naiva(Cd,CdInv),
concatenare(CdInv,[C],Inv).
La prima vedere predicatul pare inofensiv. Dar s
examinm clauza recursiv. Ea recursiv inverseaz Cd pentru a
obine CdInv i apoi alipete lista cu un singur element C la
CdInv. Ineficiena acestui predicat foarte bine se poate observa,
dac este trasat ntrebarea:
?-inversare_naiva([1,2,3],Inv)
Atunci s-ar putea vedea c se utilizeaz 28 de chemri, dintre
care 7 chemri ale predicatului inversare_naiva/2 i 21 chemri
ale predicatului concatinare/3. Numrul de chemri poate fi
redus, dac se utilizeaz un acumulator:
inversare_ac(L,Inv):-
inversare_ac(L,[],Inv).
inversare_ac([],Inv,Inv).
inversare_ac([C|Cd],Rest,Inv):-
inversare_ac(Cd,[C|Rest],Inv).
Aceast versiune pornete cu argumentul doi al
predicatului inversare_ac/3 egal cu [] i apoi utilizeaz
constructorul de list, adugnd C (la argumentul doi) la fiecare
pas al recursiei. Condiia de terminare specific c argumentele
doi i trei se unific.
Trasnd aceast versiune, se poate observa c ea conine
doar 8 chemri. Este evident, c versiunea ce utilizeaz
acumulatorul este eficient ca i variantele cu coada recursiv, n
timp ce versiunea naiv nu este eficient.
nceptorii n Prolog consider c e mai uor de elaborat
predicate definite prin reguli ce nu au cozi recursive i nu
utilizeaz acumulatoarele. Dar acumulnd experien, cu timpul
aceast obinuin prevaleaz mai puin.