Sunteți pe pagina 1din 537

Introducere in programarea

nonprocedurala (PNP)

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
Motivatia necesitatii PNP

• In cadrul paradigmelor de programare un loc


aparte il ocupa programarea nonprocedurala.

• Programarea nonprocedurala cuprinde:


▫ Programarea logica
▫ Programarea functionala.

• Programarea nonprocedurala mai poarta


numele de programare declarativa.
Plasarea PNP

Programare

Nonprocedur Orientata pe
Procedurala Concurenta ...
ala obiecte

Logica Functionala
Avantajele programelor PNP

Sunt orientate catre implementari logice.


Sunt extrem de potrivite pentru sistemele expert
apartinand domeniului inteligentei artificiale.
Sunt usor de analizat, transformat, verificat,
intretinut.
In general, sunt eficiente si competitive in
comparatie cu programele nedeclarative.
Programul este mai degraba “intrebat” decat
executat.
Dezavantajele
programelor PNP
Nu sunt usor implementabile si utilizabile pentru
algoritmii matematici de cautare si optimizare din
cadrul inteligentei artificiale.

Nu sunt cele mai potrivite pentru exprimarea


algoritmilor folositi in industrie.

Au mecanisme mai putin directe decat ale celor


nedeclarative si sunt considerate de multe ori mai
“neprietenoase”.
Programare LOGICA versus programare
FUNCTIONALA. Schema
Programare logica Programare functionala
• Adunarea a doua numere • Adunarea a doua numere

Out Directionat

aduna aduna

Nedirectionat

In In In/Out In In
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Adunarea a doua numere • Adunarea a doua numere

aduna aduna

3 4 X= 3 4
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Adunarea a doua numere • Adunarea a doua numere

aduna aduna

3 4 X=7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Scrierea simbolica
Programare logica Programare functionala
• Adunarea a doua numere • Adunarea a doua numere

aduna  In × In × In/Out aduna(In, In)  Out

aduna(3, 4, X) aduna(3, 4)

X=7 7
Programare LOGICA versus programare
FUNCTIONALA. Schema
Programare logica Programare functionala
• Patratul sumei a doua numere • Patratul sumei a doua numere
Out

patrat patrat
In
In In/Out Out

aduna aduna

In In In/Out In In
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Patratul sumei a doua numere • Patratul sumei a doua numere

patrat patrat

aduna aduna

3 4 3 4
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Patratul sumei a doua numere • Patratul sumei a doua numere

patrat patrat

aduna aduna

3 4 X=7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Patratul sumei a doua numere • Patratul sumei a doua numere
49

patrat patrat

Y = 49 7

aduna aduna

3 4 X=7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Scrierea simbolica
Programare logica Programare functionala
• Patratul sumei a doua numere • Patratul sumei a doua numere

aduna(3, 4, X), patrat(X, Y) patrat(aduna(3, 4))

X=7 patrat(7, Y) patrat(7)

X=7 Y = 49 49
Programare LOGICA versus programare
FUNCTIONALA. Schema
Programare logica Programare functionala
• Testarea egalitatii • Testarea egalitatii
succes/esec

succes/esec =

In Out

aduna aduna

In In In In In
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Testarea egalitatii • Testarea egalitatii

aduna aduna

3 4 7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Testarea egalitatii • Testarea egalitatii

succes =

7 7

aduna aduna

3 4 7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Schema. Exemplu
Programare logica Programare functionala
• Testarea egalitatii • Testarea egalitatii
succes

succes =

7 7

aduna aduna

3 4 7 3 4
Programare LOGICA versus programare
FUNCTIONALA. Scrierea simbolica
Programare logica Programare functionala
• Testarea egalitatii • Testarea egalitatii

aduna(3, 4, 7) 7 = aduna(3, 4)

succes 7=7

succes
Scopurile acestui curs

Programare logica Programare functionala


• Notiuni avansate ale • Notiuni de baza ale limbajului
limbajului PROLOG. LISP.

• Implementari pretabile • Implementari pretabile


programarii logice. programarii functionale.

• Intelegerea particularitatilor • Intelegerea particularitatilor


programarii logice. programarii functionale.

• Observarea diferentelor fata de • Observarea diferentelor fata de


programarea functionala. programarea logica.
Consecintele acestui curs
• Cunostinte elementare asupra programarii
neprocedurale.
• Intelegerea diferentelor fata de programarea
nedeclarativa.
• Observarea tipurilor de probleme care sunt
pretabile programarii neprocedurale si a celor
care sunt potrivite programarii nedeclarative.
• Obtinerea unei note bune la examen
▫ Proba practica (proba laborator) 50%
 Dezvoltarea unui program in Prolog
 Dezvoltarea unui program in Lisp
▫ Test grila (proba scrisa) 50%
PROLOG
• Numele este o abreviere pentru PROgrammation en
LOGique.

• Prima versiune a fost creata in 1972 de catre Alain Colmerauer


si Philippe Roussel, bazandu-se pe interpretarea clauzelor
Horn data de catre Robert Kowalski.

• Vom folosi versiunea Swi-Prolog.

• Bibliografie:
▫ Leon Sterling, Ehud Shapiro, The Art of Prolog, Second Edition:
Advanced Programming Techniques (Logic Programming), MIT
Press, 1994.
▫ Internet.
LISP
• Numele provine de la LISt Processing.

• Codul LISP se scrie sub forma listelor parantetizate (sau s -


expresii).

• Prima versiune a aparut in 1958 si a fost dezvoltata de catre


Steve Russell, Timothy P. Hart si Mike Levin, bazandu-se pe
calculul lambda al lui Alonzo Church.

• Vom folosi versiunea CLISP.

• Bibliografie:
▫ Stuart C.Shapiro, Common Lisp: An Interactive Approach,
Computer Science Press, 1992.
▫ Internet.
I know what you did last summer… term
• A trecut mult de anul trecut…

• Nu prea mai tinem minte de atunci…

• Prolog, am mai facut noi Prolog?...

• A, Prolog, limbajul acela urat…

• Sa recapitulam asadar notiunile de baza din


Prolog.
Ce trebuie sa ne amintim din Prolog?
• Fisierul Prolog
• Descrierea relatiilor
▫ Fapte
▫ Reguli
• Numere, atomi, variabile, operatii
• Unificare
• Recursivitate
• Liste
▫ Accesare elemente
▫ Unificare
Fisierul Prolog
• Se deschide programul Notepad.
• Se acceseaza File / Save As.
• In cadrul optiunii File Name se va scrie numele
fisierului, urmat de extensia .pl:
▫ nume.pl, de exemplu.
• In final, in campul Save as Type se completeaza cu
All Files.
• Se face dublu-click pe fisier:
▫ Directorul curent devine cel in care se afla fisierul
▫ Fisierul este compilat
• Daca se mai opereaza modificari asupra sa,
compilarea se face prin [nume].
Fara extensia .pl!
Descrierea relatiilor in Prolog
Numele relatiilor
In Prolog,
incep cu litera
orice
mica!
• Faptele – ceea ce stim despre problema: predicat se
incheie cu
▫ nume_relatie(arg1, arg2, …, argN). punct!

▫ nume_relatie este numele relaţiei (al


predicatului) iar arg1, arg2, … - argumentele.

▫ N reprezintă aritatea predicatului nume_relatie.

▫ Exemplu: frate(dan, maria).

▫ Un predicat de aritate 0 se poate defini simplu:


nume_predicat.
Descrierea relatiilor in Prolog
• Regulile (legaturi logice) – definesc predicate pe
baza altor predicate
▫ nume_relatie(arg1, …, argN) :-
nume_relatie_1(…), …, nume_relatie_M(…).
• Două predicate care au acelaşi nume, dar un
număr diferit de argumente se consideră că sunt
predicate diferite.
• Fiecare predicat dintr-un program este definit
de existenţa uneia sau mai multor clauze.
• Clauzele care definesc acelasi predicat se afla
una lângă alta în fişierul sursă.
Tipuri de termeni in Prolog
• Argumentele unui predicat Prolog se numesc
termeni si pot avea urmatoarele tipuri:

▫ Numere pozitive sau negative.

▫ Atomii – text care începe cu literă mică.

▫ Variabilele – încep cu literă mare sau underline


(_).
Tipuri de termeni in Prolog
• Numerele:
▫ -12, 7.1, 24

• Atomii:
▫ Sunt alcatuiti de obicei din litere şi cifre, având
primul caracter o literă mică.
▫ salut, douaCuvinteAlaturate, un_atom, a2 sunt
atomi
▫ nu-este-atom, 5nu, _faraunderline, Literamare nu
sunt atomi
▫ ’acesta-este-atom’, ’inca un atom’, ’Atom’ - atomi
Tipuri de termeni in Prolog
• Variabilele:
▫ Sunt asemănatoare atomilor, cu excepţia că ele
încep cu literă mare sau cu underline (_).
▫ Variabila, _variabila, Alta_vaRiabila2 sunt
variabile.
• Variabila anonima:
▫ Sunt variabilele care incep cu underline (_).
▫ Apare in doua cazuri:
 Cand valoarea unei variabile nu este folosita in
cadrul unui clauze.
 In lucrul cu predicatul fail.
Variabila anonima - continuare
• Variabila nu este folosita in cadrul unui predicat:
semn(X, 1) :- X >= 0.
semn(_X, -1).

• In lucrul cu predicatul fail:


parinte(andrei, dan).
parinte(andrei, laura).
copii(Tata) :- parinte(Tata, Copil), tab(2),
write_ln(Copil), fail.
copii(_OriceVariabila).
Operaţii matematice in Prolog
• Pentru evaluarea unei expresii aritmetice, se
foloseste predicatul predefinit is:
▫ X is <expresie aritmetică>.
▫ suma(N1, N2, S) :- S is N1 + N2.

• Operatori matematici utilizati:


▫ <, >, =<, >=, == (sau =), =\= (sau \=).
▫ Ultimii doi operatori sunt pentru a verifica
egalitatea dintre două numere, respectiv pentru a
verifica dacă două numere sunt diferite.
Operaţii matematice in Prolog
• Scrierea 2+3 nu reprezintă o instrucţiune care
păstrează rezultatul acestei adunări.
• Reprezintă mai degrabă atomul ‘adunarea lui 2 cu 3’.
• Termenul 2+3 este diferit de termenul 4+1.
numar(3).
numar(4).
numar(5).
? – numar(2 + 1).
No
? – X is 2 + 1, numar(X).
X=3
Yes
Unificarea in Prolog
• Reprezinta modul în care Prologul realizează
potrivirile între termeni:
▫ X = marian.
 Yes
▫ marian = andrei.
 No
▫ place(maria, X) = place(Y, andrei).
 Y = maria, X = andrei
▫ f(X, a) = f(a, X).
 X=a
▫ place(maria, X) = place(X, andrei).
 No
Recursivitatea in Prolog
• Programarea în Prolog depinde foarte mult de
acest principiu.
• Prolog-ul este important si fiindca ne invata sa
gandim recursiv.
• Recursivitatea implică definirea unui predicat în
funcţie de el însuşi.
• Mecanismul recursivitatii consta in faptul ca
întotdeauna definim predicatul la o scală mai
mică.
• Este echivalentă cu demonstrarea prin inducţie
din matematică.
Recursivitatea in Prolog
• O definitie recursiva are doua parti:
▫ Conditia elementara.
▫ Partea recursiva.
• Condiţia elementară defineşte un caz simplu,
care ştim că este întotdeauna adevărat.
• Partea recursivă simplifică problema eliminând
iniţial un anumit grad de complexitate şi apoi
apelându-se ea însăşi.
• La fiecare nivel, condiţia elementară este
verificată:
▫ dacă s-a ajuns la ea, recursivitatea se încheie;
▫ altfel, recursivitatea continuă.
Recursivitatea in Prolog
• Factorialul:

factorial(0,1).
factorial(N,F) :- N>0, N1 is N-1, factorial(N1,F1), F
is N * F1.

?- factorial(3, X).
X=6
Recursivitatea in Prolog
• Implementarea functiilor recursive, de exemplu,
Fibonacci:

f(1) = 1, f(2) = 1, f(n) = f(n - 1) + f(n - 2)

fibonacci(1, 1).
fibonacci(2, 1).
fibonacci(N, F):- N1 is N – 1, N2 is N – 2,
fibonacci(N1, F1), fibonacci(N2, F2), F is F1 + F2.

?- fibonacci(3, X).
X=2
Liste in Prolog

• Data viitoare…
Liste in Prolog

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2/56

Liste
• Sunt structuri care se folosesc pentru reprezentarea unei
secvenţe ordonate de termeni Prolog.
▫ [doar, un, exemplu]
▫ []
▫ [a, b, c, d, e]
▫ [1, 21, 4, -17]
▫ [4, [6], c, [-1, 12]]
• Virgula folosită în construcţia listelor este doar un
separator de argumente.
• Listele sunt secvenţe ordonate de termeni Prolog, deci
lista [a,b,c] nu este aceeaşi cu lista [a, c, b].
3/56

Accesarea elementelor unei liste


• Pentru a accesa elementele unei liste, putem
împarţi lista în două părţi: primul element (dacă
există unul!) şi restul listei.
• Ce se obtine din apelul urmator:
▫ ? - [X|Y] = [a, b, c, d].

X=a
Y = [b, c, d]
4/56

Accesarea elementelor unei liste


• Ce se obtine din apelul urmator:
▫ ? - [X|Y] = [a, b, c, d].

X=a
Y = [b, c, d]

• Primul element din listă, a, se numeşte şi capul listei


(HEAD).

• Lista formată prin ştergerea capului reprezintă coada


listei iniţiale (TAIL): [b, c, d].
▫ Ea poate fi mai departe prelucrată si este referita prin
variabila Y.
5/56

Adaugarea unui element la o lista


• Presupunem că avem lista X = [b, c, d] şi vrem să
adăugăm elementul a la începutul listei X:
• Lista_rezultat = [a | X].
• Cum scriem programul care face acest lucru?

adauga(A, X, Y):- Y=[A|X].

X – lista initiala
A – elementul de adaugat
Y – lista care se va obtine
6/56

Adaugarea de elemente la o lista


• Se pot adăuga (sau elimina) şi mai multe
elemente odată.

• Dacă dorim, de exemplu, să adăugam la


începutul listei X elementele a, b şi c, pentru
obţinerea unei liste Y:

• Y = [a, b, c | X]
7/56

Stergere de elemente dintr-o lista

• Dacă vrem să luam trei elemente din capul listei


X astfel încât lista rămasă Y să poată fi folosită
mai departe în cadrul programului:

• X = [A, B, C | Y]

• Lista vidă se notează cu [ ] si, evident, aceasta nu


poate fi descompusă.
8/56

Unificarea la liste
• [a, b, c] = [c, a, b]
▫ întoarce No pentru că ordinea termenilor
contează

• [X] = [a, b, c]
▫ întoarce No pentru că cele două liste au lungimi
diferite

• [X|Y] = [doar, un ,exemplu]


▫ întoarce răspuns pozitiv şi X = doar, iar Y = [un,
exemplu]
9/56

Unificarea la liste
• [X,Y|Z] = [a, b, c, d]
▫ întoarce Yes şi X = a, Y = b, Z = [c, d]

• [X|Y] = []
▫ întoarce No pentru că lista vidă nu poate fi
descompusă

• [X|Y] = [[a, [b, c]],d]


▫ întoarce Yes şi X = [a,[b, c]] şi Y = [d]

• [X|Y] = [a]
▫ întoarce Yes şi X = a, Y = []
10/56

Unificarea la liste
• [a, b | X] = [A, B, c]

• [a, b] = [b, a]

• [a | [b, c]] = [a, b, c]

• [a, [b, c]] = [a, b, c]

• [a, X] = [X, b]
11/56

Unificarea la liste
• [a | []] = [X]

• [a, b, X, c] = [A, B, Y]

• [H | T] = [[a, b], [c, d]]

• [[X],Y] = [a, b]
12/56

Afisarea elementelor unei liste


• Pentru afişarea elementelor unei liste vom face
apel la recursivitate.

• Luăm elementele din listă, unul câte unul, şi le


afişăm.

• Momentul de oprire a algoritmului este atunci


când lista va fi goală.

• Aşadar, condiţia elementară (de oprire a


recursivităţii) va fi când lista e vidă.
13/56

Afisarea elementelor unei liste


Adauga trei
spatii

afis([]).
afis([Prim | Rest]) :- write(Prim), tab(3),
afis(Rest).
14/56

Apartenenta unui element la o lista


Are 2
argumente

• Vom defini predicatul apartine/2, unde primul


argument reprezintă elementul pentru care
verificăm apartenenţa, iar al doilea este lista.

• X aparţine listei dacă este capul listei sau dacă


aparţine coadei acesteia.
15/56

Apartenenta unui element la o lista


• Altfel spus, clauza care testeaza unificarea dintre elementul
dat si capul listei reprezintă condiţia de oprire a recursivităţii,
clauza care se verifică la fiecare reapelare a predicatului
apartine.

apartine(X, [X | _]).
apartine(X, [Y | Rest]) :- apartine(X, Rest).

?- apartine (3, [1, 3, 2]).


Yes.

?- apartine (4, [1, 3, 2]).


No.
16/56

Apartenenta unui element la o lista


17/56

Numarul elementelor dintr-o lista

• Dacă lista este vidă, numarul elementelor sale


este zero: aceasta este condiţia de oprire a
recursivităţii.

• În clauza recursiva, primul element din listă nu


ne interesează, vrem doar să îl eliminăm ca să
numărăm câte elemente are lista rămasă.

• N-ul curent va fi, de fiecare data, egal cu 1 plus


numărul elementelor din lista rămasă.
18/56

Numarul elementelor dintr-o lista

nr_elem([], 0).
nr_elem([_ | Rest], N) :- nr_elem(Rest, N1), N is
N1 + 1.

?- nr_elem([1, 2, 3], X).

X=3
19/56

Suma elementelor dintr-o lista


• Dacă lista este vidă, suma elementelor sale este
zero: aceasta este condiţia de oprire a
recursivităţii.

• În clauza recursiva, primul element din listă ne


interesează de data aceasta, dupa care calculam
suma elementelor din lista rămasă.

• S-ul curent va fi, de fiecare data, egal cu


elementul curent plus suma elementelor din
lista rămasă.
20/56

Suma elementelor dintr-o lista

suma([], 0).
suma([P|Rest], S) :- suma(Rest, S1), S is S1 + P.

?- suma([1, 2, 3], X).

X=6
21/56

Media elementelor unei liste


• Media unei liste se calculeaza drept suma
elementelor din lista / numarul acestora.

media(L) :- nr_elem(L, N), suma(L, S), Media is


S/N, write('Media este '), write(Media).

?- media([1, 2, 3]).

Media este 2.
22/56

Ultimul element dintr-o lista

• Condiţia de oprire este ca X să fie ultimul


element din listă – coada listei este o listă vidă.

• Daca restul listei curente nu este vid, ignoram


capul listei.
23/56

Ultimul element dintr-o lista


Echivalent putem scrie
ultim([X],X).

ultim([X | []], X).


ultim([_ | Rest], X) :- ultim(Rest, X).

?- ultim([1, 2, 3], X).

X=3
24/56

Maximul unei liste


• Consideram primul element al listei ca fiind maximul.

• Apelam un alt predicat ce are drept argumente lista


ramasa si elementul considerat.

• Parcurgem restul listei; daca gasim un element (capul


listei curente) mai mare decat maximul, acesta va deveni
noul maxim.

• Altfel, mergem mai departe in restul listei.

• Recursivitatea se incheie cand ajung la lista vida si afisez


argumentul corespunzator maximului.
25/56

Maximul unei liste


max([P|Rest]) :- Max = P, max1(Rest, Max).

max1([], Max) :- write('Maximul este '),


write(Max).
max1([P|R], Max) :- P > Max, max1(R, P); max1(R,
Max).

?- max([4, 2, 5, 1]).
Maximul este 5.
26/56

Pozitia pe care se afla maximul unei


liste

• Consideram primul element al listei ca fiind


maximul si stabilim pozitia maximului drept 1.

• Apelam un alt predicat ce are drept argumente:


▫ lista ramasa
▫ elementul considerat drept maxim
▫ pozitia pe care se afla acesta
▫ si un contor care va numara elementele.
27/56

Pozitia pe care se afla maximul unei


liste
• Parcurgem lista; daca gasim un element (capul noii
liste) mai mare decat maximul:
▫ acesta va deveni noul maxim
▫ pozitia pe care se afla maximul devine contorul curent
▫ si se incrementeaza contorul.

• Altfel, mergem mai departe in restul listei,


incrementand contorul.

• Recursivitatea se incheie cand ajung la lista vida si


afisez argumentul corespunzator pozitiei pe care se
afla maximul.
28/56

Pozitia maximului unei liste


poz_max([P|Rest]) :- poz_max(Rest, P, 1, 1).

poz_max([], _, _, Poz) :- write('Maximul se gaseste pe pozitia '),


write(Poz).
poz_max([P|R], Max, Contor, Poz) :- Contor1 is Contor + 1, Max < P,
poz_max(R, P, Contor1, Contor1).
poz_max([_|R], Max, Contor, Poz) :- Contor1 is Contor + 1,
poz_max(R, Max,
Contor1, Poz).

?- poz_max([4, 2, 5, 1]).
Maximul se gaseste pe pozitia 3
29/56

Compararea lungimilor a doua liste


• Predicatul va avea ca
argumente doua liste si va
intoarce unul din urmatoarele
raspunsuri posibile:
▫ Liste egale
▫ Prima este mai lunga
▫ A doua este mai lunga

• Se parcurg cele doua liste,


ignorand capetele, pana una
din ele se termina.
30/56

Compararea lungimilor a doua liste

compar([],[]):-write('Cele doua liste au acelasi


numar de elemente.').
compar(_L, []):-write('Prima lista are mai multe
elemente decat cea de a
doua!').
compar([], _L):-write('A doua lista are mai multe
elemente decat prima!').
compar([_X|R1], [_Y|R2]) :- compar(R1, R2).
31/56

Compararea lungimilor a doua liste


?- compar([1,2,3], [4, 5]).

Prima lista e mai lunga

?- compar([1,2], [4, 5]).

Cele doua liste sunt egale

?- compar([1,2], [3, 4, 5]).

A doua lista e mai lunga


32/56

Inversarea unei liste


• Se apeleaza un predicat care, pe langa lista
initiala si lista in care depunem rezultatul,
contine si o lista temporara care este initial vida.

• Capul listei curente se adauga la inceputul listei


temporare – acesta era initial goala, deci
elementele se vor adauga in ordine inversa.

• Cand lista care trebuie inversata devine vida,


unificam lista finala cu cea temporara.
33/56

Inversarea unei liste

inv(L, Linv) :- inv1(L, [], Linv).

inv1([], L, L).
inv1([X|Rest], Temp, L) :- inv1(Rest, [X|Temp],
L).

?- inv([1, 2, 3], L).


L = [3, 2, 1]
34/56

Interclasarea a doua liste

• Ce presupune interclasarea?

• Avem doua liste care trebuie unite intr-una


singura.

• Cele doua liste trebuie sa fie ordonate crescator.

• Elementele listei rezultate trebuie sa fie de


asemenea in ordine crescatoare.
35/56

Interclasarea a doua liste


• Capetele celor doua liste ce trebuie unite se
compara.

• Cel mai mic dintre ele se va adauga la lista


rezultat.

• Daca sunt egale, se adauga doar o data.

• Daca una dintre ele este vida, lista rezultat este


cealalta.
36/56

Interclasarea a doua liste


interclasez([], L, L).
interclasez(L, [], L).
interclasez([P1|R1], [P2|R2], [P1|R3]) :- P1 < P2,
interclasez(R1, [P2|R2], R3).
interclasez([P1|R1], [P1|R2], [P1|R3]) :-
interclasez(R1,
R2, R3).
interclasez(R1, [P2|R2], [P2|R3]) :- interclasez(R1,

R2, R3).

?- interclasez([1, 3, 7], [2, 3, 4, 8], L).


L = [1, 2, 3, 4, 7, 8]
37/56

Prefixul si sufixul unei liste


• Prefixul unei liste reprezinta primele N elemente
ale unei liste.

• Sufixul unei liste reprezinta ultimele M elemente


ale unei liste.

• Prefixul ar putea fi asociat cu primele slide-uri


din lista care formeaza acest curs.

• Iar sufixul… ultimele slide-uri (sufixul e mai


frumos )
38/56

Prefixul unei liste


• Pentru a testa daca o lista e prefixul altei liste,
compar element cu element cele doua liste.

• Adica, verific daca elementul cap al unei liste


prefix este egal cu cel al listei complete.

• Daca raspunsul este afirmativ, merg mai


departe.

• Prima lista e prefix a celei de-a doua daca, la un


moment dat, lista prefix se incheie.
39/56

Prefixul unei liste

prefix([], _L).
prefix([X|R1], [X|R2]) :- prefix(R1, R2).

?- prefix([1,2], [1, 2, 3]).


Yes

?- prefix([1,3], [1, 2,3]).


No
40/56

Sufixul unei liste


• Pentru a testa daca o lista e sufixul altei liste,
parcurg lista completa pana intalnesc exact lista
sufix.

• Adica, scot elementul cap al listei mari, pana


cand cele doua liste sunt egale.

• Recursivitatea se opreste deci cand cele doua


argumente sunt egale.
41/56

Sufixul unei liste

sufix(L, L).
sufix(L, [_Y|Rest]) :- sufix(L, Rest).

?- sufix([1,2,3],[1,2]).
No

?- sufix([1, 2, 3], [3]).


Yes
42/56

Sublista unei liste

• O lista va fi sublista unei alte liste daca este


sufixul prefixului listei mari.

Prefixul listei mari

Sufixul prefixului listei mari


43/56

Sublista unei liste


sublista(L1, L2):-prefix(L, L2), sufix(L1, L).

?- sublista([1,2], [3,1,2,5,6]).
Yes
?- sublista([1,2], [3, 1, 4, 5, 2])
No L2

L1
44/56

9 0 3 2 4 5 6 7

Pozitii pare, pozitii impare

• Enunt problema:
▫ Se dă o listă: să se obţină două liste din aceasta astfel
încât prima din ele să conţină elementele de pe
poziţiile pare iar a doua pe cele de pe poziţiile impare.

• Vom avea asadar o singura lista ca argument de


intrare si doua liste ca argumente de iesire.

• Tratam intai situatia in care lista data contine un


singur element, apoi cand lista are doua elemente.
45/56

9 0 3 2 4 5 6 7

Pozitii pare, pozitii impare


parimpar([X], [], [X]).
parimpar([X, Y],[Y], [X]).
parimpar([X, Y|R], [Y|R1], [X|R2]) :- parimpar(R, R1, R2).

• In cea de a treia clauza, mutam primul element X din lista


data in lista a treia, iar pe cel de-al doilea, Y, in a doua lista.

• Interogare:
▫ ? – pare([ion, marius, mananca, invata, mere, prolog], P, I).
 P = [marius, invata, prolog]
 I = [ion, mananca, mere]
46/56

9 0 3 2 4 5 6 7

Pozitii pare, pozitii impare


parimpar([X], [], [X]).
parimpar([X, Y],[Y], [X]).
parimpar([X, Y|R], [Y|R1], [X|R2]) :- parimpar(R, R1, R2).

• Cea de a treia clauza a programului poate fi scrisa


sub forma urmatoare:
parimpar([X, Y|R], Pare, Impare):-parimpar(R, Pare1, Impare1),
Pare = [Y|Pare1], Impare=[X|Impare1].
47/56

Pozitia i dintr-o lista


• Enuntul problemei:
▫ Dându-se o listă şi un număr întreg pozitiv i, să se
găsească elementul aflat pe poziţia i în listă.
• Avem doua argumente de intrare, o lista si un
numar care da pozitia care ne intereseaza.

• Cum rezolvam problema: scadem i-ul cu cate o


unitate si, in acelasi timp, scoatem cate un
element din lista. Cand i-ul este 1, primul element
din lista este cel cautat.
48/56

Pozitia i dintr-o lista


pozi([X|_], 1, X).
pozi([_A|R], I, X) :- I1 is I - 1, pozi(R, I1, X).

• Asadar, al treilea argument al predicatului pozi ia


valoarea primului element din lista atunci cand al doilea
argument este egal cu 1.
• Altfel, i este scazut, iar din lista data ca primul argument
extragem un element la apelarea recursiva.
• Interogarea:
▫ ? - pozi([mere, portocale, pere, gutui], 2, Ce).
 Ce = portocale
49/56

1 4 6 7 8 9 0 3 2 4 5 6 7

Pozitia unui element intr-o lista

• Enunt problema:
▫ Având date o listă şi un element care aparţine
acestei liste, să se specifice pe ce poziţie este situat
elementul în lista dată.
• Avem doua argumente de intrare:
▫ Lista in care se gaseste elementul
▫ Elementul pentru care trebuie sa gasim pozitia
• Vom mai construi un predicat care sa contina si
o variabila contor care este initial 1.
50/56

1 4 6 7 8 9 0 3 2 4 5 6 7

Pozitia unui element intr-o lista


pozx(L, X, P):- pozx(L, X, 1, P).
pozx([X|_], X, P, P).
pozx([_|R], X, C, P) :- C1 is C + 1, pozx(R, X, C1, P).

• Predicatul pozx cu 4 argumente este vazut ca unul diferit de


cel cu acelasi nume dar cu 3 argumente.
• Conditia de oprire: primul element din lista corespunde cu
elementul dat.
▫ In acest moment, contorul se unifica cu variabila aflata pe
pozitia patru.
• In concluzie, ideea este ca se scot elemente din lista pana
cand pe prima pozitie se afla chiar ceea ce cautam.
51/56

1 4 6 7 8 9 0 3 2 4 5 6 7

Pozitia unui element intr-o lista

pozx(L, X, P):- pozx(L, X, 1, P).


pozx([X|_], X, P, P).
pozx([_|R], X, C, P) :- C1 is C + 1, pozx(R, X, C1, P).

• Interogarea:
▫ ? – pozx([ion, petre, marin, olivia], marin, P).
 P=3
52/56

Stergerea aparitiilor unui element


dintr-o lista
• Enunt problema:
▫ Să se şteargă toate apariţiile unui element dintr-o
listă.
• Avem doua argumente de intrare:
▫ Lista din care se vor sterge aparitiile unui element
▫ Elementul care trebuie sters
• Argumentul de iesire va fi noua lista care
nu va mai contine elementul dat.
53/56

Stergerea aparitiilor unui element


dintr-o lista
sterg([], _, []).
sterg([N|Rest], N, Rez) :- sterg(Rest, N, Rez).
sterg([M|Rest], N, [M|Rez]) :- sterg(Rest, N, Rez).
• Se parcurge lista data element cu element si, daca elementul aflat
in capul listei este chiar cel cautat, nu se copiaza in lista rezultat.
Altfel, se copiaza.
• Atunci cand lista data este vida, si lista rezultat este initializata cu
lista vida.
• Interogare:
▫ ? – sterg([1, 4, 6, 8, 6, 12, 6], 6, L).
 L = [1, 4, 8, 12]
54/56

Eliminarea duplicatelor
• Enunt problema:
▫ Scrieţi un predicat Prolog care să realizeze
eliminarea duplicatelor dintr-o listă dată.
• Argument de intrare:
▫ O lista data
• Argument de iesire:
▫ Lista rezultata prin eliminarea duplicatelor din
lista data.
• Vom face uz de predicatul apartine/2 prezentat
mai devreme.
55/56

Eliminarea duplicatelor
duplicate([], []).
duplicate([X|R1], L) :- apartine(X, R1), duplicate(R1, L).
duplicate([X|R1], [X|R2]) :- duplicate(R1, R2).

• Luam fiecare element din prima lista si verificam daca


apartine restului listei (adica daca mai apare in lista).
▫ Daca nu mai apare, atunci il adaugam in lista rezultat
▫ Altfel, nu il adaugam.

• Interogare
▫ ? – duplicate([7, 9, 7, 11, 11], L).
 L = [9, 7, 11]
56/56

Pe saptamana viitoare!
Structuri compuse in Prolog

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2/41

Ce sunt structurile?
• Cea mai mare parte din informaţia pe care vrem să o
reprezentăm într-un program este compusă, adică ea
constă din entităţi care au mai multe atribute diferite
▫ De exemplu, entitatea persoană poate avea mai multe
atribute precum vârstă, înăltime, greutate etc.
• În programele realizate până acum, am utilizat numai fapte şi
reguli care foloseau structuri de date simple.
▫ Argumentele predicatelor folosite au fost atomi sau numere.
• Aceste date simple pot însă fi combinate pentru a construi
tipuri de date complexe numite structuri.
3/41

Ce sunt structurile?
• O structură constă dintr-un functor şi un număr fix
de argumente:
structura(arg1, arg2, …, argn)
• Fiind un termen, structura poate să apară în cadrul
unei clauze oriunde poate apărea o variabilă sau o
constantă.
• Precum toţi ceilalţi termeni folosiţi anterior, nici
structurile nu trebuie declarate
▫ Le folosim direct în cadrul programului, acolo unde
avem nevoie de ele.
4/41

Ce sunt structurile?
• Deşi arată precum predicatele, nu funcţionează
ca acestea.
▫ Predicatele sunt relatii, iar structurile sunt
obiecte.
▫ Diferenţa dintre cele două este realizată de către
Prolog după locul în care apar într-o clauză.
• Structurile nu apar niciodată singure, ci doar ca
argumente pentru predicate.
5/41

Exemplu de utilizare a unor structuri


student(adrian, prezente(8), proiect(bun)).
student(marius, prezente(2), proiect(copiat)).
student(andreea, prezente(9), proiect(bun)).
student(ovidiu, prezente(4), proiect(slab)).

• Daca vrem sa aflam care sunt studentii care pot sa ia o nota


peste 7, putem folosi interogarea:
▫ ? – student(X, prezente(Y), proiect(bun)), Y > 6, writeln(X), fail.
adrian
andreea
6/41

Exemplu de utilizare a unor structuri


student(adrian, prezente(8), proiect(bun)).
student(marius, prezente(2), proiect(copiat)).
student(andreea, prezente(9), proiect(bun)).
student(ovidiu, prezente(4), proiect(slab)).

• Daca vrem sa aflam care sunt studentii care nu vor intra in


examen, putem folosi interogarea:
▫ ? – student(X, prezente(_Y), proiect(copiat)), writeln(X), fail.
marius
Underline-ul indică faptul că
nu suntem interesaţi de
valoarea cu care este unificat
acest câmp.
7/41

Alt exemplu de utilizare a unor structuri

• O structura poate avea si mai mult de un singur


argument (cum a fost cazul in exemplul precedent).
• Exemplu:

are(ionut, calculator(asus, ‘3 Ghz’, ‘RAM 1 GB’)).


are(ovidiu, calculator(hp, ‘2.7 Ghz’, ‘RAM 512 MB’)).
are(ovidiu, calculator(dell, ‘2.4 Ghz’, ‘RAM 512 MB’)).
8/41

Arbori binari
• Un arbore binar are proprietatea că pentru un nod
părinte:
▫ fiecare nod aflat în partea stângă a sa are o valoare numerică
mai mică decât a sa şi
▫ fiecare nod aflat în partea dreaptă a nodului părinte are o
valoare mai mare decât a sa.

4 11

3 6 9 16
9/41

Arbori binari
• Pentru reprezentarea în Prolog, presupunem că fiecare nod are
câte două legături către alţi arbori:
▫ una către subarborele stâng
▫ una către subarborele drept
arb(8, arb(4, arb(3, n, n), arb(6, n, n)), arb(11, arb(9, n, n), arb(16, n, n)))

8
Se reprezinta
asadar sub forma
unei structuri
compuse. 4 11

3 6 9 16
10/41

Arbori binari
• Scrieţi un predicat Prolog care să calculeze suma
elementelor arborelui.
suma(n, 0).
suma(arb(R, n, n), R).
suma(arb(Radacina, S, D), Suma) :- suma(S, S1),
suma(D, S2), Suma is Radacina + S1 + S2.
11/41

Arbori binari
• Scrieţi un predicat Prolog care să verifice
existenta unui număr dat într-un arbore binar.
cauta(n, _):- write(‘Nu exista.’).
cauta(arb(X, _, _), X):- write(‘Numarul exista.’).
cauta(arb(Rad, S, _D), X) :- X < Rad, cauta(S, X).
cauta(arb(_Rad, _S, D), X) :- cauta(D, X).
12/41

Arbori binari
• Scrieţi predicate Prolog care să realizeze parcurgerea
unui arbore binar în preordine, în inordine şi în
postordine.

• Preordine:
1. Vizitam radacina.
2. Vizitam subarborele stang in preordine.
3. Vizitam subarborele drept in preordine.

• Parcurgerea:
▫ 8, 4, 3, 6, 11, 9, 16.
13/41

Parcurgerea in
preordine
• preord(n).
• preord(arb(Rad, S, D)) :-writeln(Rad),
preord(S),
preord(D).
14/41

Parcurgerea in inordine
• Inordine:
1. Vizitam subarborele stang in inordine.
2. Vizitam radacina.
3. Vizitam subarborele drept in inordine.

• Parcurgerea:
▫ 3, 4, 6, 8, 9, 11, 16
15/41

Parcurgerea in postordine
• Postordine:
1. Vizitam subarborele stang in postordine.
2. Vizitam subarborele drept in postordine.
3. Vizitam radacina.

• Parcurgerea:
▫ 3, 6, 4, 9, 16, 11, 8
16/41

Inordine si postordine?

• Cum se implementeaza in Prolog?

▫ Solutia este triviala si ramane ca tema.


17/41

Inserarea unui element intr-un arbore binar

• Realizaţi un predicat Prolog care să realizeze


inserarea unui element in cadrul arborelui.
Unde este
8 locul
meu?...

4 11

10

3 6 9 16
18/41

Inserarea unui element intr-un arbore binar


• Realizaţi un predicat Prolog care să realizeze
inserarea unui element in cadrul arborelui.
8

4 11

3 6 9 16

10
19/41

Inserarea unui element intr-un arbore binar

ins(Val, n, arb(Val, n, n)).


ins(Val, arb(Rad, L_T, R_T), Rez) :- Val < Rad,
ins(Val, L_T, Rez1), Rez = arb(Rad, Rez1, R_T).
ins(Val, arb(Rad, L_T, R_T), Rez) :- Val > Rad,
ins(Val, R_T, Rez1), Rez = arb(Rad, L_T, Rez1).
Intrari si iesiri in Prolog

Altfel spus, cum citim dintr-un fisier si cum scriem


intr-unul folosind Prologul.
21/41

Intrari si iesiri in Prolog


• Orice sursă sau destinaţie de date este numită în

Prolog stream (canal de intrare, sau de ieşire).
• Cele mai utilizate predicate pentru citirea şi scrierea
datelor sunt, evident, read şi write.

Vom folosi si altele cand


vom invata sa lucram cu
caractere si string-uri in
Prolog.
22/41

Intrari si iesiri in Prolog

• Atat write/1 cat si read/1 au un singur argument


şi folosesc canalul curent de ieşire, respectiv de
intrare.
▫ Cele predefinite sunt ecranul şi tastatura.
 ? – read(X), write(‘Am citit termenul’), tab(1),
write(X).

▫ La citire, dupa scrierea termenului care trebuie


citit, se pune punct (.).
23/41

Intrari in Prolog
• Pentru a citi dintr-un fişier nu trebuie decât să
facem din fişierul respectiv canalul curent.

• Predicatele care fac acest lucru sunt:


▫ see(F) – fişierul dat ca argument devine fişier de
intrare curent.
 Fişierul F este deschis pentru citire, iar pointerul de
fişier este poziţionat la începutul lui.
▫ seen – închide fişierul de intrare curent şi stabileşte
canalul de intrare tastatura.
24/41

Iesiri in Prolog
• Pentru a scrie într-un fişier trebuie să facem din fişierul
respectiv canalul curent.
• Predicatele care fac acest lucru sunt:
▫ tell(F) – deschide fişierul F pentru scriere şi îl face fişier
de ieşire curent.
 Dacă fişierul există deja, conţinutul său este şters, altfel, fişierul
este creat.
▫ append(F) – face acelasi luru ca si tell/1, cu deosebirea
ca, daca fisierul exista, continutul sau nu este sters, ci se
scrie in continuare.
▫ told – închide fişierul de ieşire curent stabilind ca stream
de ieşire ecranul.
25/41

Intrari/iesiri in Prolog
• Predicatul read(-Term) citeşte în variabila Term următorul
termen din fişierul de intrare.
▫ Termenul citit trebuie să se termine cu caracterul punct în
cadrul fişierului.

• Există o constantă specială în Prolog, end_of_file, care este


returnată atunci când s-au citit toate datele dintr-un fişier.
• Un fişier Prolog poate fi încărcat din interiorul unui alt
fişier, cu ajutorul predicatului consult(NumeFisier).
• nl provoacă trecerea la o linie următoare (new line), iar
tab(N) adaugă N spaţii faţă de poziţia la care este situat
pointerul în canalul de ieşire curent.
26/41

Exemplu

• Avem fişierul de intrare in.txt care conţine câte un număr


urmat de caracterul punct pe fiecare linie. Scrieţi în fişierul
pare.txt numerele conţinute în in.txt care sunt pare, iar în
impare.txt numerele care sunt impare.
27/41

Separarea elementelor pare de cele


impare
separ([], [], []).
separ([P|R1], L2, [P|R2]) :- Rest is P mod 2, Rest = 1,
separ(R1, L2, R2).
separ([P|R1], [P|R2], L2) :- separ(R1, R2, L2).
28/41

Afisarea elementelor unei liste

afis([]).
afis([P|R]) :- write(P), nl, afis(R).
29/41

Citirea din in.txt si scrierea in pare.txt


si impare.txt

exemplu :- see('in.txt'), citesc([]), seen, write('Totul este gata').

citesc(L) :- read(N), N \= end_of_file, append(L, [N], Rez),


citesc(Rez).
citesc(L) :- separ(L, Pare, Impare), pare(Pare), impare(Impare).

pare(L) :- tell('pare.txt'), afis(L), told.


impare(L) :- tell('impare.txt'), afis(L), told.
30/41

Rularea programului
• Scriem mai intai intr-un fisier text cateva numere urmate
de caracterul punct, fiecare aflate pe cate un rand.
▫ Il salvam la aceeasi locatie unde se afla si programul.
31/41

Rularea programului
32/41

Alt exemplu
• Dacă fisierul in.txt conţine câte un număr urmat de
punct pe fiecare linie, scrieţi un predicat Prolog care să
introducă în fişierul suma.txt mesajul Suma este urmat
de valoarea sumei numerelor din in.txt.

• Cum se rezolva?
▫ Avem acelasi cod de la programul precedent pentru citirea
elementelor intr-o lista.
▫ Se calculeaza apoi suma elementelor din lista si se scrie
aceasta in fisierul suma.txt.
33/41

Sau… gasim o alta rezolvare


suma:- see('in.txt'), tell('suma.txt'), calc(0), told, seen.

calc(S) :- read(N), N \= end_of_file, S1 is S + N, calc(S1).


calc(S) :- write('Suma este '), write(S).
34/41

Selectati numerele prime

• Având fişierul in.txt dat ca in exemplele


precedente, realizaţi un predicat Prolog care să
scrie în fişierul prime.txt numai acele numere
care sunt prime.
▫ Citim toate numerele intr-o lista, ca la exemplul
initial (predicatul citesc/1), apoi selectam
numerele prime intr-o lista pe care o afisam apoi
in fisierul tinta.
 Simplu, nu?
35/41

Verificarea daca un numar este prim

prim(N) :- prim(N, 2).

prim(N, K) :- I is N/2, K > I.


prim(N, K) :- R is N mod K, R \= 0, K1 is K + 1,
prim(N, K1).
Fara sa folosim o lista temporara…
prime :- see('in.txt'), tell('prime.txt'), told, calcul, seen.

calcul :- read(X), X \= end_of_file, verific(X), calcul.


calcul.

verific(X) :- prim(X), append('prime.txt'), write_ln(X),


told.
verific(_).
37/41

Selectati numerele prime


38/41

Ordonarea unui sir de numere

• Având în fişierul de intrare in.txt câte un număr urmat de


caracterul punct pe fiecare linie, construiţi un predicat
Prolog care să scrie în fişierul ordonat.txt şirul de numere
ordonat crescător.
▫ Citim numerele din in.txt intr-o lista, le ordonam in alta lista
si le scriem apoi in fisierul ordonat.txt.
▫ O sa facem in continuare numai ordonarea elementelor unei
liste.
▫ Pentru aceasta, vom folosi metoda quicksort care utilizeaza
mecanismul divide et impera.
39/41

Ordonarea elementelor unei liste

sortez([], []).
sortez([P|Rest], Lrez):- selectez(P, Rest, Mici, Mari),
sortez(Mici, MiciSort), sortez(Mari, MariSort),
append(MiciSort, [P|MariSort], Lrez).

selectez(_, [], [], []).


selectez(P, [P1|Rest], [P1|Mici], Mari):- P1 < P,
selectez(P, Rest, Mici, Mari).
selectez(P, [P1|Rest], Mici, [P1|Mari]):- selectez(P, Rest,
Mici, Mari).
40/41

Rularea algoritmului de sortare

• Citirea elementelor dintr-un fisier si scrierea elementelor


sortate in fisierul sortat urmeaza sa fie facute de…
41/41

Pe saptamana viitoare!
Backtracking

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2/32

Backtracking - exemplu

• Sa consideram programul:

natural(0).
natural(N) :- natural(N1), N is N1 + 1.

• Ce va genera Prolog-ul in urma unui apel de


forma:

? – natural(N), write(N), nl, sleep(1), fail.


3/32

Backtracking - exemplu
• Se va genera un ciclu infinit:
▫ numerele succesive sunt generate prin
backtracking.

• Primul număr natural este generat şi afişat.

• Apoi fail forţeaza backtracking-ul să acţioneze.

• A doua clauză este apelată, generând numere


naturale succesive.
4/32

Generare numere naturale


5/32

Un alt exemplu de backtracking

prefixN(N, [N]).
prefixN(N, [N|L]) :- N1 is N + 1, prefixN(N1, L).

• Pentru a folosi backtrackingul, avem nevoie de


un apel de forma:

? - prefixN(1, L), write(L), nl, sleep(1), fail.

• Acesta generează o infinitate de liste care încep


cu valoarea N.
6/32

Generare de liste incrementale


care incep cu un element dat
7/32

Backtracking

• Backtracking-ul în Prolog este asadar foarte uşor


de utilizat.

• Predicatul fail/0 este cel care duce la forţarea


backtrackingului:
▫ el întoarce întotdeauna un rezultat negativ şi duce
la reapelarea predicatelor care se află înaintea sa,
ori de câte ori este posibil.
8/32

Permutari

• Sa generam permutările muţimii {a, b, c, d}


folosind backtracking-ul in acest scop.
• Mai intai definim cele 4 litere:

litera(a).
litera(b).
litera(c).
litera(d).
9/32

Permutari
permutare(X, Y, Z, T) :- litera(X), litera(Y), litera(Z),
litera(T), X \= Y, X \=Z, Y \= Z, X \=T, Y\=T,Z \=T.

permutari :- permutare(X, Y, Z, T), write(X), tab(1),


write(Y), tab(1), write(Z), tab(1), write(T), nl, fail.
permutari.

• Apelăm simplu, predicatul permutari:

? – permutari.
10/32

Permutari
• Iata permutarile rezultate:
11/32

Taietura (Cut)
• Uneori însă, din cauza backtracking-ului, sunt
întoarse rezultate nedorite.

• Pentru evitarea acestora, Prologul ne vine în


ajutor, prin ceea ce se numeşte taietura.

• Cu ajutorul acestui procedeu, Prologul oferă


posibilitatea opririi backtrackingului.

• Procedeul se mai numeşte cut şi se notează cu


semnul exclamării (!).
12/32

Taietura (Cut)

• Tăietura face ca Prologul să blocheze toate


deciziile făcute până la momentul apariţiei sale
(adică a semnului exclamării).

• Asta înseamnă că, dacă backtracking-ul era în


desfăşurare, el va fi oprit şi nu se vor mai căuta
alte alternative pentru ce se găseşte înainte de
tăietură (!).
13/32

Exemplu
locul(1, simion).
locul(2, maria).
locul(3, sorin).
locul(3, cristian).

? – locul(3, Nume), write(Nume), nl, fail.


sorin
cristian

? – locul(3, Nume), !, write(Nume), nl, fail.


sorin
14/32

Mai adaugam:
despre(1, ' este castigatorul
concursului.'). ? – spune(3).
despre(1, ' primeste 100$.'). sorin
despre(2, ' a câştigat locul II.'). a castigat locul III
despre(2, ' primeste 50$.'). primeste 25$.
despre(3, ' a castigat locul III'). cristian
despre(3, ' primeste 25$.'). a castigat locul III
spune(Loc) :- locul(Loc, Cine), primeste 25$.
write(Cine), nl, despre(Loc,
Despre), tab(3),
write(Despre), nl, fail.
15/32

Cut
• Dacă vrem însă să ştim care este primul dintre
cei care au luat locul trei şi câteva lucruri despre
el, avem nevoie de tăietură - adăugăm la
predicatul spune/1 semnul exclamării astfel:

spune(Loc) :- locul(Loc, Cine), write(Cine), nl, !,


despre(Loc, Despre), tab(3), write(Despre), nl,
fail.

• Acum, după adăugarea făcută, backtrackingul se


va aplica numai asupra predicatelor aflate dupa
semnul exclamării.
16/32

Taietura
• Tăietura se foloseşte de obicei în interiorul unui
predicat unde Prolog-ul a găsit primul răspuns
posibil despre care se doresc mai multe detalii.
• Sau pentru a forţa un predicat să întoarcă un
răspuns negativ (adică fail) într-o anumită
situaţie şi nu vrem să caute alte soluţii.
• Tăietura însă, în general, măreşte complexitatea
codului decât să o simplifice, iar utilizarea ei este
bine să fie evitată pe cât posibil:
▫ se spune chiar că ea reprezintă goto-ul
programării logice.
1

2 3

4 5 6 7

Arbori si grafuri

1 5 9

2 4 6 8

3 7
18/32

Arbori 1

2 3

4 5 6 7

• Cum reprezentam acest arbore?


19/32

Arbori 1

2 3
% tata(X,Y) - X este tatal lui Y

tata(0,1). 4 5 6 7

tata(1,2).
tata(1,3).
tata(2,4). Aceasta confirma faptul ca
1 este radacina arborelui.
tata(2,5).
tata(2,6).
tata(3,7).
20/32

Parcurgere in adancime 1

2 3

4 5 6 7

• Care ar fi parcurgerea in adancime a acestui


arbore?

• 1, 2, 4, 5, 6, 3, 7
21/32

Parcurgere in adancime 1

Gaseste toti X care


au o anumita relatie 2 3
cu un Y dat; acestia
se pun in lista L

4 5 6 7
findall(X, relatie(X,Y), L)

parcurgere:-tata(0, Rad), p([Rad]).

p([]).
p([Nod|Rest]) :- write(Nod), tab(2), findall(D,
tata(Nod, D), LC), append(LC, Rest, Stiva),
p(Stiva).
22/32

Parcurgere in adancime 1

2 3
?-parcurgere.
4 5 6 7
23/32

Parcurgere in latime 1

2 3

4 5 6 7

• Care ar fi parcurgerea in latime a acestui arbore?

• 1, 2, 3, 4, 5, 6, 7
24/32

Parcurgere in latime 1

2 3

4 5 6 7

parcurgere_latime:-tata(0, Rad), c([Rad]).

c([]).
c([P|Rest]) :- write(P), tab(2), findall(D, tata(P,
D), LC), append(Rest, LC, Coada), c(Coada).
25/32

Parcurgere in latime 1

2 3
?-parcurgere_latime.

4 5 6 7
26/32

Grafuri orientate
1 5 9

2 4 6 8

3 7

• Cum reprezentam acest graf?


27/32

Grafuri orientate
1 5 9
marc(1, 2).
marc(1, 4). 2 4 6 8
marc(1, 5).
marc(3, 2). 3 7

marc(3, 7).
marc(4, 3).
marc(5, 6).
marc(6, 8).
marc(7, 6).
marc(9, 8).
28/32

Grafuri orientate
1 5 9

2 4 6 8

3 7

• Sa determinam toate drumurile intre doua


noduri ale acestui graf.
29/32

Grafuri orientate
1 5 9

2 4 6 8

3 7

drum :- write('Introduceti nodul de start: '),


read(X), write('Introduceti nodul destinatie: '),
read(Y), drum(X, Y, L), write_ln(L), fail.
drum.
30/32

Grafuri orientate
1 5 9

2 4 6 8

3 7

drum(X, Y, L) :- drum(X, Y, [X], L).

drum(X, X, L, L).
drum(X, Y, L, Lista) :- marc(X, Z),
not(member(Z, L)), append(L, [Z], L1), drum(Z,
Y, L1, Lista).
31/32

Grafuri orientate
1 5 9
?- drum.
2 4 6 8

3 7
32/32

Pe saptamana viitoare!
Caractere şi stringuri

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2/36

Scrierea şi citirea caracterelor

• Prologul are predicate predefinite folosite


pentru scrierea şi citirea câte unui caracter.

• Predicatul put/1 va scrie un singur caracter.

• Din păcate insă, argumentul trebuie să fie un


întreg care reprezintă caracterul ASCII.
3/36

Scrierea caracterelor
• Deci, pentru scrierea mesajului merge, ar trebui
să apelăm:

? - put(109), put(101), put(114), put(103),


put(101).

• Evident însă, oricine ar prefera să folosească:

? – write(’merge’).
4/36

Scrierea caracterelor

• Dacă folosim însă ca argument la predicatul


write/1 un mesaj scris între ghilimele:
▫ ne vor fi afişate chiar valorile întregi care
reprezintă fiecare caracter în cod ASCII.

? – write(”merge”).
[109, 101, 114, 103, 101]
5/36

Scrierea caracterelor

• Având astfel lista de valori numerice, este simplu


de realizat un predicat recursiv care sa scrie
toate caracterele, rând pe rând.

scriestringul([]).
scriestringul([P|R]) :- put(P), tab(1),

scriestringul(R).
6/36

Scrierea caracterelor
? - scriestringul(”merge”).
7/36

Citirea caracterelor

• Pentru citirea unui caracter folosim predicatul


get/1:
▫ acesta citeşte doar primul caracter din cele
introduse.

• Poate fi introdus orice caracter.


8/36

Citirea caracterelor

• La citire, nu mai trebuie să incheiem, după ce am


introdus caracterul de citit, cu punct (.).

• Punctul poate fi chiar caracterul de citit.

• Dacă citirea se face dintr-un fişier, verificarea de


sfârşit de fişier se face comparând valoarea dată
ca argument la predicatul get/1 cu –1.
9/36

Citirea caracterelor

• Predicatul citesc/0 definit în continuare, citeşte


un caracter şi afişează valoarea sa
corespunzătoare în codul ASCII:

citesc :- write('Introduceti un caracter:'), get(C),


nl, write(Valoarea caracterului '), put(C), write(’
in cod ASCII este:’), write(C).
10/36

Citirea caracterelor
?-citesc.
11/36

Predicate predefinite la stringuri


• Deja am folosit mai devreme stringuri:
▫ ele sunt atomi simpli, scrişi însă între ghilimele

• Trecerea de la atomi la lista care conţine


numere întregi în cod ASCII se face prin
intermediul predicatului

• string_to_list(+String, -Ascii) sau


• string_to_list(-String, +Ascii)
12/36

String -> List


? – string_to_list(string, Valoare).

? - string_to_list(String, [99,101,118,97]).
13/36

Concatenare stringuri

• string_concat(+String1, +String2, -String3)

• string_concat este un predicat similar lui


atom_concat/3, cu singura deosebire că
argumentul de ieşire aici este de tipul string.
▫ De subliniat faptul că argumentele de intrare sunt
date ca atomi.
14/36

Concatenare stringuri
? - string_concat(campu, lung, Tot).

?- string_concat(campu, Ce, campulung).


15/36

Lungime sir de caractere


• string_length(+String, -Lungime)

? - string_length(‘un string mare de tot’, M).

? - string_length(altceva, M).
16/36

String <-> Atom


• string_to_atom(+String, -Atom) sau
• string_to_atom(-String, +Atom)

?- string_to_atom("string de transformat", Atom).

?- string_to_atom(String, exemplu).
17/36

Determinarea subsirului unui sir


• sub_string(+String, +Start, +Lungime, -Rest, -
Substring)

• Primul argument este sirul de caractere din care


extragem un subşir.

• Start este un întreg pozitiv care dă poziţia de la care


selectăm subşirul.

• Al treilea argument dă lungimea subşirului.

• Rest este tot un întreg pozitiv care spune câte poziţii mai
sunt până la sfârşitul şirului iniţial.

• Iar ultimul argument reprezintă chiar subşirul căutat.


18/36

Determinarea subsirului unui sir


?- sub_string('Alin e tare nebun', 0, 11, Rest,
Valoare).

?- sub_string('Alin e tare nebun', _, 5, 0, Valoare).


19/36

Determinarea subsirului unui sir


?- sub_string('Alin e tare nebun', 5, Cat, 6,
Valoare).

?- sub_string('Alin e tare nebun',


Inceput,Cat,Rest,nebun).
20/36

Determinarea subsirului unui sir


• De subliniat că şi în cazul acestui predicat, ca şi
la string_concat/3:
▫ argumentele de intrare sunt atomi
▫ doar cel de ieşire reprezentând un string.

• După cum se vede din exemple:


▫ numai primul argument trebuie să fie întotdeauna
instanţiat
▫ celelalte pot fi calculate.
21/36

Transformare data si ora in string

• convert_time(+Timp, -Valoare)

• Primul argument poate fi obţinut prin apelarea


predicatului get_time/1: obţinem astfel data şi
ora curente.
22/36

Transformare data si ora in string


?- get_time(Timp), convert_time(Timp, Data), nl,
write('Data este '), write(Data).
23/36

Transformare data si ora in string

• Pentru manevrarea mai uşoară a datei şi orei,


mai există şi predicatul

• convert_time(+Timp, -Anul, -Luna, -Ziua, -Ora,


-Minute, -Secunde, -Milisecunde).
24/36

Transformare data si ora in string


?- get_time(Timp), convert_time(Timp, An, Luna,
Zi, Ora, Minute, Secunde, Milisec).
25/36

Palindrom

• Verificaţi cu ajutorul unui predicat dacă un


cuvânt dat este palindrom.

• Un palindrom este un cuvânt care are aceeaşi


formă, fie că este citit de la stânga, fie de la
dreapta.

• Exemple: capac, lupul, cojoc, rar.


26/36

Palindrom

palindrom :- write('Introduceti cuvantul de


verificat:'), read(Cuvant), string_to_list(Cuvant,
Lista), palindrom(Lista).

palindrom(Lista) :- invers(Lista, Lista), write('Da,


cuvantul este un palindrom!').
palindrom(_) :- write('Cuvantul nu este un
palindrom!').
27/36

Inversarea unei liste - recapitulare

invers(L1, L2) :- inv(L1, [], L2).

inv([], L, L).
inv([X|R], Lt, L) :- inv(R, [X|Lt], L).
28/36

Palindrom
29/36

Vocale
• Având un text introdus în fişierul intrare.txt, să
se numere câte vocale se găsesc în acesta.

• Pregatesc fisierul intrare.txt.

• Il deschid in citire.

• Colectez caracter de caracter.


▫ Daca acela care este curent este membru in
multimea data de vocale, il numar;
▫ Altfel, parcurgem fisierul mai departe.
30/36

Vocale
vocale :- see('intrare.txt'), numar(0), seen.

numar(N) :- get(Caracter), Caracter \= -1,


verific(Caracter, N, N1), numar(N1).
numar(N) :- write('Am gasit '), write(N), write('
vocale.').

verific(Caracter, N, N1) :-
string_to_list(aeiouAEIOU,Lista),
member(Caracter, Lista), N1 is N + 1.
verific(_, N, N).
31/36

Vocale
32/36

Permutari de caractere

• Având un sir de caractere introdus de la consolă,


să se genereze şi să se afişeze toate permutările
de caractere posibile.
33/36

Permutari de caractere
permut :- write('Introduceti cuvantul pentru care
generam permutarea:'), read(Cuvant),
string_to_list(Cuvant, CLista), permut(CLista).

permut(L) :- permut(L, Rez), scr(Rez), nl, fail.


permut(_).

scr([]).
scr([P|R]) :- put(P), scr(R).
34/36

Permutari de caractere
permut([], []).
permut(L, P) :- selecteaza(X, L, Rest),
permut(Rest, RestPerm), P = [X|RestPerm].

selecteaza(X, [X|R], R).


selecteaza(X, [Y|R], [Y|R1]) :- selecteaza(X, R,
R1).
35/36

Permutari de caractere
36/36

Pe saptamana viitoare!
Baze de date dinamice

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2/61

Baze de date dinamice


• Este evident faptul ca un program Prolog este o
bază de date ce conţine predicate.

• Până acum, am introdus clauzele pentru


predicatele realizate de noi din interiorul
programului.

• Prologul ne oferă însă posibilitatea să manevrăm


baza de date în mod direct şi ne oferă predicate
predefinite care fac acest lucru.
3/61

Adaugare clauze

• assert(X) – adaugă clauza X ca ultima clauză a


predicatului din care face parte.

• asserta(X) – clauza X este adăugată ca prima


clauză a predicatului.

• assertz(X) – acelaşi efect cu assert(X).


4/61

Retragere clauze

• retract(X) – scoate clauza X din baza de date.

• retractall(X) – sunt eliminate toate faptele sau


regulile din baza de date pentru care capul
clauzei este unificat cu X.
5/61

Exemplu
• Presupunem că avem următoarele fapte într-un
fişier Prolog:

copil(ionut).
copil(marian).
copil(simona).

• Presupunem că ei sunt introduşi în ordinea


vârstei lor.
6/61

Apelare
• Cu un apel de forma:

? - copil(C).

• obţinem, evident, C = ionut.


• Pentru:

? - copil(C), write(C), nl, fail.

• vor fi afişaţi toţi copiii găsiţi.


7/61

Adaugare clauze
• Dacă se iveşte un nou copil, şi anume, Iulia, trebuie
să avem în vedere faptul că ea este cea mai mică.
Avem nevoie de un apel de forma:

? – asserta(copil(iulia)).

• Putem verifica acum faptul că Iulia a fost inserată în


baza de date dinamică folosind una din cele două
apelări de mai sus, pentru afişarea primului copil,
respectiv pentru afişarea tuturor copiilor:

? - copil(C), write(C), nl, fail.


8/61

Retragere clauze
• Să luăm în continuare cazul în care Simona a
trecut de vârsta la care mai poate fi numită copil.
Pentru a o elimina din baza de date, apelăm:

? – retract(copil(simona)).

• Din nou putem verifica dacă a fost eliminată,


afişând toţi copiii:

? - copil(C), write(C), nl, fail.


9/61

Adaugare clauze
• Să presupunem, în continuare, că toate
personajele prezentate mai sus, cât timp au fost
copii, au fost buni prieteni.

• Pentru a introduce predicatul prieteni/2 descris


mai sus, chiar de la consola Prologului, folosim
următoarea comandă:

? – assert((prieteni(X,Y):-copil(X), copil(Y))).
10/61

Retragere clauze

• Când toţi copiii au trecut de vârsta majoratului,


pentru a informa şi programul nostru că ei nu
mai sunt copii, vom apela:

? – retractall(copil(_)).
11/61

Exerciţiu
• Presupunem că avem următoarea schemă a unei
case:
12/61

Exerciţiu
• Definiţi:
▫ legăturile dintre camere (eventual printr-un
predicat numit uşă)

▫ locul unde se află subiectul (presupunem că iniţial


se află în birou).
13/61

Exerciţiu
• La un moment dat, dacă se apeleaza un predicat
numit mergi/1, sistemul să spună:
▫ care este camera în care se află subiectul

▫ dacă nu poate merge în camera specificată ca


argument,

▫ iar dacă poate, unde poate merge mai departe de


acolo.
14/61

Exemplu
?- mergi(bucatarie).
Esti in birou
Ai mers in bucatarie
Poti merge in:
birou
sufragerie
subsol

? – mergi(hol).
Nu poti ajunge direct din bucatarie in hol
15/61

Rezolvare
:-dynamic aici/1.

usa(birou, hol).
usa(bucatarie, birou).
usa(hol, sufragerie).
usa(bucatarie, subsol). Trebuie specificat faptul ca
vom defini un predicat in mod
usa(sufragerie, bucatarie). dinamic.
Se declara numele predicatului
si numarul sau de argumente.
conectat(X,Y) :- usa(X,Y).
conectat(X,Y) :- usa(Y,X).

aici(birou).
16/61

Rezolvare
mergi(Camera) :- aici(Aici), not(conectat(Aici,
Camera)), write('Esti in '), write(Aici), nl, write('Nu
poti ajunge din '), write(Aici), write(' direct in '),
write(Camera).

mergi(Camera) :- aici(Aici), write('Esti in '),


write(Aici), nl, write('Ai mers in '), write(Camera),
retract(aici(Aici)), assert(aici(Camera)), nl,
write('Poti merge in '), nl, unde.

unde :- aici(Aici), conectat(Aici, Camera), tab(2),


write(Camera), nl, fail.
unde.
17/61

Rezultate
18/61

Alt exemplu

• Construiţi predicatul declar/1 care are ca


argument o listă de numere întregi şi care
realizează două alte predicate, par/1 şi impar/1
care vor avea ca argumente numai numerele care
sunt pare, respectiv numai elementele care sunt
impare din cadrul listei.
19/61

Rezolvare

:-dynamic par/1.
:-dynamic impar/1.
declar([]).
declar([P|R]) :- P1 is P mod 2, P1 = 0,
assert(par(P)), declar(R).
declar([P|R]) :- assert(impar(P)), declar(R).
20/61

Rezultate
21/61

Functii de memorare
• O functie de memorare salveaza rezultatele unor
subcalcule pentru a putea fi folosite direct, in
apelari viitoare, fara a mai fi recalculate.

• Prototipul unei astfel de functii este


retin(deductie(arg1, …, argn))

• Implementarea se realizeaza astfel:


retin(deductie(arg1, …, argn)):-deductie(arg1, …,
argn), asserta(deductie(arg1, …, argn)).
22/61

Functii de memorare
• Se verifica veridicitatea faptului respectiv si, in caz
afirmativ, se introduce in memoria Prolog-ului.

retin(deductie(arg1, …, argn)):-deductie(arg1, …,
argn), asserta(deductie(arg1, …, argn)).

• Data urmatoare cand se doreste atingerea scopului


(afirmatia din capul clauzei), solutia va fi gasita
direct in memorie, fara a repeta calculele.
23/61

Turnurile din Hanoi


• Enunt: Se dau N discuri si 3
cuie: A, B, C.

• Se cere sa se mute toate


discurile de pe cuiul A pe
cuiul B, folosindu-se drept
cui intermediar C. A C B
• Discurile sunt intotdeauna
asezate unul peste altul in
ordinea crescatoare a
marimii.
24/61

Turnurile din Hanoi - solutie


• Problema se rezolva recursiv.

• Se muta N – 1 discuri de pe cuiul A pe cuiul C.

• Se muta apoi cele N – 1 discuri de pe C pe B.

• In final, se aseaza cuiul 1 de pe A pe B.

• Cand avem 1 disc, se muta de pe A pe B.


25/61

Turnurile din Hanoi - Prolog


hanoi1(N):-hanoi1(N, a, b, c, L), afis(L).

hanoi1(N,A,B,_C,[(A,B)]):-N = 1.
hanoi1(N,A,B,C,M):-N > 1, N1 is N - 1,
hanoi1(N1,A,C,B,M1),
hanoi1(N1,C,B,A,M2),append(M1, [(A, B)|M2],
M).

afis([]).
afis([X|Rest]):-writeln(X), afis(Rest).
26/61

Turnurile din Hanoi – functii de


memorare
:-dynamic(hanoi/5).

hanoi(N):-hanoi(N, a, b, c, L), afis(L).

hanoi(N,A,B,_C,[(A,B)]):-N = 1.
hanoi(N,A,B,C,M):-N > 1, N1 is N - 1,
retin(hanoi(N1,A,C,B,M1)),
hanoi(N1,C,B,A,M2),append(M1, [(A, B)|M2], M).

retin(P):-P, asserta(P).
27/61

Turnurile din Hanoi


• Se utilizeaza functiile memo pentru a imbunatati
performanta programului initial.

• Solutia acestei probleme, cand sunt N discuri,


necesita 2^N – 1 mutari.

• Modul de solutionare al problemei rezolva in mod


repetat subprobleme prin mutarea unui numar
identic de discuri.
28/61

Turnurile din Hanoi


• O functie de memorare poate fi folosita pentru a
retine mutarile facute pentru fiecare subproblema
cu un numar mai mic de discuri.

• Cand se ajunge la reapelarea unui fapt care a fost


deja calculat, se poate folosi secventa de mutari
deja retinuta in memorie, in loc de a le recalcula.

• Se memoreaza prima clauza recursiva, de muta N


– 1 discuri, a predicatului hanoi si poate fi folosita
de a doua apelare recursiva a sa pentru N – 1
discuri.
29/61

Rulare

A C B
Predicatul REPEAT
31/61

Repeat
• Dacă o anumită clauză trebuie satisfăcută de
mai multe ori, facem apel la predicatul fail care
face ca Prologul să încerce să găsească toate
soluţiile (presupunând că nu folosim tăietura).

• Totuşi, în unele situaţii, este necesar să repetăm


numai o anumită parte de program, înainte de a
continua procesul cu restul clauzei.

• Acest gen de situaţii apar atunci când vrem să


realizăm operaţii iterative, precum citirea dintr-
un fişier ori la realizarea unui meniu.
32/61

Repeat
• În general, predicatul repeat este folosit în construcţii de
forma:

nume_predicat :- repeat, % încep bucla


afisez_meniu, % afişez meniul pentru
utilizator
citesc_optiunea(N),% citesc data introdusă de
către utilizator
validez_optiunea(N), % verific dacă există în
cadrul meniului
execut_cerinta(N), % realizez ce era
specificat în
meniu
pentru N
verific_oprirea(N), % verific dacă este
condiţia de terminare
!. % oprirea iteraţiei.
33/61

Exemplu
• Următorul predicat citesc/0 face citirea de la
tastatură a unor valori pe care le afişează apoi
utilizatorului.
• Procesul se termină atunci când utilizatorul
introduce gata.

citesc :- repeat, meniu, read(X), write('Am citit '),


write(X), write('!'), nl, X == gata, !.

meniu :- nl, write('---------'), nl, write('Exemplu


banal!'), nl, write('Orice introduceti, afisam!'), nl,
write('Pentru oprirea repeat-ului, tastati gata.'), nl,
write('---------'), nl, nl.
34/61

Exemplu
35/61

Alt exemplu

• Realizaţi un predicat care să simuleze predicatul


predefinit consult/1.
36/61

Rezolvare

compilez(Fisier) :- see(Fisier), repeat,


read(X),
assert(X),
X == end_of_file,
!, seen.
37/61

Rezultate
38/61

Alt exemplu

• Avem relaţiile existente într-o familie date


printr-un singur predicat, persoana(Nume, Sex,
Copii), unde primul argument reprezintă numele
persoanei în cauză, al doilea ne spune dacă este
bărbat sau femeie, iar al treilea este o listă
(poate fi vidă) care conţine numele copiilor
persoanei la care ne referim.
39/61

Alt exemplu
• Utilizatorului îi va fi prezentat un meniu care îi
permite să facă următoarele operaţii:

▫ Să adauge o persoană (întreabă dacă este


bărbat sau femeie); se validează iniţial dacă
există sau nu în baza de cunoştinţe introdusă.
Presupunem că nu există două persoane cu
acelaşi nume.

▫ Să şteargă o persoană din baza de cunoştinţe.

▫ Să adauge informaţia că X este copilul lui Y.


40/61

Alt exemplu

• Continuare meniu:

▫ Eliminarea lui X din lista de copii a lui Y.

▫ Eliminarea tuturor persoanelor din bază.

▫ O opţiune care să-i permită utilizatorului să


salveze baza de cunoştinţe într-un fişier,
respectiv să o citească dintr-un fişier.
41/61

Faptele
persoana(andrei, barbat, [cristi, elena]).
persoana(cristi, barbat, [adriana, marius, ovidiu]).
persoana(elena, femeie, [ana]).
persoana(marius, barbat, []).
persoana(ovidiu, barbat, []).
persoana(george, barbat, []).
persoana(adriana, femeie, []).
persoana(ana, femeie, [george]).
42/61

Adaugarea unei noi persoane

adaugare :- write('Numele celui ce va fi adaugat: '),


read(Nume), not(persoana(Nume, _, _)),
write('Sexul '), read(Sex),
assert(persoana(Nume, Sex, [])), nl,
write('Persoana a fost adaugata!').

adaugare :- write('Exista deja in baza noastra!').


43/61

Adaugarea unei noi persoane


44/61

Stergerea unei persoane

stergere :- write('Numele celui ce va fi sters: '),


read(Nume), persoana(Nume, _, _),
retract(persoana(Nume, _, _)), write(Nume),
write(' a fost sters!').

stergere :- nl, write('Nu exista in baza de


cunostinte!').
45/61

Stergerea unei persoane


46/61

Adaugarea copilului X la Y

copil(_X, Y) :- not(persoana(Y, _, _)), write(Y),


write(' nu exista in baza noastra de cunostinte!').

copil(X, Y) :- retract(persoana(Y, SexY, CopiiY)),


assert(persoana(Y, SexY, [X|CopiiY])), nl,
write('Acum '), write(X), write(' este copilul lui
'), write(Y).
47/61

Adaugarea copilului X la Y
48/61

Eliminarea copilului X de la Y

elimcopil(_X, Y) :- not(persoana(Y, _, _)),


write(Y), write(' nu exista in baza noastra de
cunostinte!').

elimcopil(X, Y) :- persoana(Y, _, Copii),


not(member(X, Copii)), write(X), write(' nici nu
e copilul lui '), write(Y), write('.').
49/61

Eliminarea copilului X de la Y

elimcopil(X, Y) :- retract(persoana(Y, SexY,


CopiiY)), elim(X, CopiiY, CopiiiY),
assert(persoana(Y, SexY, CopiiiY)), write('Acum
'), write(X), write(' nu mai este copilul lui '),
write(Y).

elim(_, [], []).


elim(X, [X|R], L) :- elim(X, R, L).
elim(X, [P|R], [P|L]) :- elim(X, R, L).
50/61

Eliminarea copilului X de la Y
51/61

Eliminarea tuturor persoanelor din


baza
elimintot :- retractall(persoana(_, _, _)), nl,
write('Baza de cunostinte este acum goala!').
52/61

Salvarea bazei de cunostinte intr-


un fisier

salvez(Fisier) :- tell(Fisier), salvez, told,


write('Fisierul a fost salvat').

salvez :- persoana(Nume, Sex, Copii),


write('persoana('), write(Nume), write(','),
write(Sex), write(','), write(Copii), write(').'), nl,
fail.

salvez.
53/61

Salvarea bazei de cunostinte intr-


un fisier
54/61

Incarcarea unui fisier in memorie


deschid(Fisier) :- consult(Fisier), nl,
write('Fisierul '), write(Fisier), write(' a fost
incarcat.'), nl.
55/61

Meniul
meniu :- nl, nl, tab(15), write('Selectati un numar
din cadrul meniului'), nl, nl, write('1. Listare
baza de cunostinte.'), nl, write('2. Adaugare
persoana.'), nl, write('3. Eliminare persoana.'),
nl, write('4. Adaugarea lui X ca si copil al lui Y.'),
nl, write('5. Stergerea lui X ca si copil al lui Y.'),
nl, write('6. Eliminarea tuturor persoanelor.'), nl,
write('7. Salvarea bazei de cunostinte intr-un
fisier Prolog.'), nl, write('8. Incarcarea bazei de
cunostinte dintr-un fisier Prolog.'), nl, write('9.
Incheiere.'), nl, nl.
56/61

Partea principala
principal :- repeat, meniu, read(Optiune),
proceseaza(Optiune), nl, Optiune == 9, !.

proceseaza(1) :- persoana(Nume, Sex, []),


write(Nume), write(' este '), write(Sex), write(' si nu
are copii.'), nl, fail.
proceseaza(1) :- persoana(Nume, barbat, Copii), Copii
\= [], write(Nume), write(' este barbat iar copiii lui
sunt: '), afiscopii(Copii), nl, fail.
proceseaza(1) :- persoana(Nume, femeie, Copii), Copii
\= [], write(Nume), write(' este femeie iar copiii ei
sunt: '), afiscopii(Copii), nl, fail.
proceseaza(1).
57/61

Partea principala
proceseaza(2) :- adaugare, !.
proceseaza(3) :- stergere, !.
proceseaza(4) :- write('Copilul X: '), read(X),
write('Parintele Y: '), read(Y), copil(X, Y), !.
proceseaza(5) :- write('Copilul X: '), read(X),
write('Parintele Y: '), read(Y), elimcopil(X, Y), !.
proceseaza(6) :- elimintot,!.
58/61

Partea principala
proceseaza(7) :- write('Numele fisierului Prolog
(introduceti-l intre apostrofuri si cu extensia pl):
'), read(NumeFis), salvez(NumeFis), !.
proceseaza(8) :- write('Numele fisierului din care
se face incarcarea (intre apostrofuri si cu
extensia pl): '), read(Fis), deschid(Fis),!.
proceseaza(9).
59/61

Partea principala

afiscopii([]).
afiscopii([P|R]) :- write(P), write(', '), afiscopii(R).
60/61

Rulare
61/61

Pe saptamana viitoare!
Programare functionala.
Fundamentele limbajului LISP

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
Bibliografie

• Stuart C. Shapiro, Common Lisp: An Interactive


Approach, Computer Science Press, 1992.

• Internet.
Introducere
• Vom opera cu mediul standard al limbajului
LISP.

• Acest lucru presupune ca vom lucra in


COMMON LISP.

• Common Lisp aduce o interfata simpla, de tip


DOS.

• In particular, vom lucra cu implementarea


CLISP 2.30.
Introducere
• In momentul in care pornim Common Lisp, ne
vom afla deja in fata prompterului Lisp.

• Prompterul va astepta sa introducem ceea ce, in


cadrul programarii functionale, poarta numele
de S-expresie (expresie simbolica).

• Dupa ce S-expresia este scrisa, apasam tasta


ENTER.
Ciclul citire-evaluare-scriere al
Lisp
• Atunci cand dam Lisp-ului o S-expresie, acesta va
produce urmatorii pasi:

▫ Va citi S-expresia.

▫ Va interpreta S-expresia drept reprezentarea scrisa


a unui forme (obiect Lisp ce trebuie evaluat).

▫ Va evalua forma drept alt (sau poate chiar acelasi)


obiect valoare.
Ciclul citire-evaluare-scriere al
Lisp
▫ Va alege o reprezentare scrisa pentru obiectul
valoare.

▫ Va scrie reprezentarea scrisa pe care a ales-o.

• Dupa ce intoarce valoarea, prompterul Lisp va


reaparea si va astepta o noua expresie.
Ciclul citire-evaluare-scriere al
Lisp
• Acesta este modul de folosire al Lisp:

▫ Utilizatorul introduce reprezentarea scrisa a unei


forme.

▫ Lisp o evalueaza.

▫ Apoi, trimite inapoi o reprezentare scrisa a valorii


formei.
Un prim exemplu
• O S-expresie simpla pe care o vom introduce este
numeralul in scriere araba, 3.

• Aceasta este una din reprezentarile scrise pe care


le folosim pentru numarul 3.

• Oamenii folosesc si numeralul in scriere romana


III.

• Aceasta este deci distinctia pe care o face si Lisp


intre un obiect si diferitele sale posibilitati de
reprezentare scrisa.
Exemplu
• Lisp interpreteaza numeralul 3 ca reprezentand
numarul 3.

• Evalueaza aceasta forma – adica obiectul


numeric 3.

• In Lisp, numerele sunt evaluate in ele insele.

• Lisp va alege o reprezentare scrisa pentru 3 si va


utiliza, de asemenea, numeralul arab 3.
Interactiunea cu Lisp
Debugger-ul din Lisp
• Daca in introducerea unei S-expresii se face vreo
greseala, va intra debugger-ul Lisp-ului.

• Acesta mai poarta numele si de ciclu (sau pachet)


break.

• Acesta arata ca un prompter Lisp obisnuit, doar ca


exista aici niste comenzi speciale pentru a obtine
informatii despre ce presupune eroarea.

• Deocamdata, vom parasi aceste bucle break, tastand


:q.
Debugger-ul din Lisp
Terminarea sesiunii de Lisp
Numere in Lisp
• Numerele sunt unul dintre tipurile de baza ale
Lisp-ului.

• Se pot folosi numere intregi sau reale.

• In cadrul intregilor, nu putem folosi insa virgule


sau spatii:
▫ 12 345 sau 12,345 sunt reprezentari incorecte de
intregi.
▫ Vom scrie direct 12345.
Numere in Lisp
• Pentru a scrie un intreg negativ, vom insera
semnul “-” in fata sa, iar pentru unul pozitiv
putem de asemenea pune semnul “+”:
▫ -34, +25 sunt expresii corecte de intregi.

• Un intreg se poate termina cu “.” – acesta va fi


citit drept intreg:
▫ 12. va fi egal cu a scrie 12.
▫ 12.0 va fi insa interpretat drept real.
Numere in Lisp

• Numerele reale sunt construite cu ajutorul


semnului “.” si cu cel putin o cifra dupa punct:
▫ 12.9
▫ 13.0

• Pot fi scrise si sub forma stiintifica, cu semnul de


exponent:
▫ 0.34e-2 – care inseamna 0.34 × 10-2.
Numere in Lisp
Numere in Lisp

Caracterul “;” se
foloseste pentru a
comenta o anumita
parte . Rezulta ca ce se
afla dupa el este
ignorat.
Liste in Lisp
• LisP = List Processing

• Care este reprezentarea scrisa a unei liste?

• Conform lui S. C. Shapiro, definitia unei S-


expresii lista este:
▫ O paranteza stanga urmata de zero sau mai multe
S-expresii urmate de o paranteza dreapta este o S-
expresie lista.

• S-expresiile se delimiteaza una de alta prin


spatii.
Exemple
• (1 3.2 2 4)

• (1 (2 3.3) 4)

• ()

• ((1 3.2 2 4))

• (())
Liste
• In acest moment, Lisp-ul citeste expresia care
este data de utilizator si incearca sa o evalueze.

• Pana la a evalua o lista, ii vom cere Lisp-ului


doar sa ne afiseze lista introdusa.

• Putem impiedica evaluarea unei liste si, in loc, sa


obtinem printarea ei folosind semnul de apostrof
inaintea S-expresiei lista.
Exemple de interactiune cu Lisp

Modul de reprezentare scrisa


ales de Lisp pentru lista vida.
Liste
• Lisp-ul va ignora de asemenea spatiile in plus
sau ENTER-urile.

• Daca toate parantezele deschise nu sunt inchise


de utilizator, Lisp-ul va astepta in continuare
paranteze dreapta.

• Se pot pune mai multe paranteze dreapta decat


stanga; Lisp-ul le va ignora pe cele in plus.
Exemple
Expresii aritmetice in Lisp
• Evaluarea obiectelor lista este operatia de baza
in Lisp.

• Conform lui S. C. Shapiro:


▫ Valoarea unei liste este cea obtinuta prin aplicarea
functiei denumita de primul argument (membru)
al listei asupra valorilor celorlalti membri ai listei.

• Vom incepe evaluarea listelor cu ajutorul


operatorilor matematici de baza: +, -, *, /.
Exemplu
Notatia prefixa Cambridge

• Formatul sub care expresiile aritmetice sunt


scrise sub forma de lista poarta numele de
notatie prefixa Cambridge.

• Numele provine de la cel care a dezvoltat aceasta


notatie – John McCarthy de la MIT, Cambridge,
MA – si de la faptul ca operatorul “prefixeaza”
(este inaintea) operanzilor sai.
Notatia prefixa Cambridge
• Termenul a fost preluat de la notatia poloneza
prefixa, unde functia e scrisa inaintea
argumentelor sale.

• Acest format difera de cel matematic clasic de tip


infix:
▫ In care operatorul este scris intre operanzii sai (de
ex. 12 + 4)
▫ sau in care functia este scrisa inainte de argumente
dar nu in paranteza cu ele (de ex. f(x, y)).
▫ Aceasta din urma se va scrie in Lisp sub forma: (f x
y)
Notatia prefixa Cambridge

• Avantajul major al acestei notatii este ca scrierea


ramane foarte simplu de utilizat indiferent de
numarul de argumente:
▫ 1,
▫2
▫ sau chiar mai multe ducand la operatii succesive
Exemple de interactiune

Am uitat sa inseram un spatiu!


Evaluarea listelor

• Daca argumentele functiilor aritmetice sunt


intregi, rezultatul va fi intreg.

• Daca unul dintre argumente este real, atunci


rezultatul va fi real.
Evaluarea listelor
• Exceptie se face daca incercam sa impartim un
intreg la un alt intreg si valoarea rezultata nu
este exacta:
▫ Rezultatul va fi ceea ce poarta numele de fractie: 2
numere separate de semnul “/”, pozitive sau
negative.
▫ Fractia va fi reprezentata de catre Lisp sub forma
simplificata.

• Si utilizatorul poate introduce fractii, chiar si sub


forma nesimplificata.
Exemple
Evaluarea listelor
• Putem avea expresii aritmetice incluse in alte
expresii aritmetice – cum este natural in
matematica, de exemplu, 5 × (3 + 4).

• In Lisp, aceasta expresie se va scrie sub forma:


(× 5 (+ 3 4))

• In schimb, 5 × 3 + 4 se scrie:
(+ (× 5 3) 4)

• In general f(x, g(y)) se va scrie sub forma:


(f x (g y))
Interactiune
Exercitiu
• Sa calculam radacinile ecuatiei:

2x2 + 7x + 5 = 0
• Acestea sunt:

 7  7  4 25
2

2 2
Exercitiu
• Le vom scrie Lisp-ului sub forma:

• (/ (+ -7.0 (sqrt (- (expt 7 2) (* 4 2 5)))) (* 2 2))

Functia radical – Functia ridicare


un singur la putere – 2
• si argument argumente

• (/ (- -7.0 (sqrt (- (expt 7 2) (* 4 2 5)))) (* 2 2))


Interactiune
Testarea egalitatii
• Verificarea egalitatii se face cu operatorul “=”.

• I se pot da 1, 2 sau mai multe argumente.

• Argumentele pot fi de tipuri numerice diferite –


“=” testeaza numai egalitatea numerica.

• Intoarce TRUE (T) daca numerele sunt egale si


FALSE (NIL) altfel.
Interactiune
Exercitiu

• Utilizand Lisp-ul, gasiti valorile pentru:


▫ (25 + 30) × 15/2

▫ 6 × 3.1416

▫ Media numerelor 5, 6.7, -23.2, 75 si 100.3


Pe saptamana viitoare…
Fundamentele limbajului LISP (2)

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2

Stringuri si caractere
• Un string este un vector de caractere.

• Este scris de catre Lisp ca secventa caracterelor


sale inconjurata de ghilimele.

• Ca si numerele, stringurile sunt evaluate in ele


insele.

• Sa scriem un string la prompterul de Lisp.

> “Acesta este un string.”


3

Exemplu
4

Stringuri
• Un string poate fi oricat de lung si poate contine
caractere precum ENTER.

• In afara numerelor, Common Lisp are de


asemenea functii care opereaza si asupra
obiectelor de alt tip.

• De exemplu, pentru a afla numarul de caractere


dintr-un string, se foloseste functia length.
5

Exemplu
6

Stringuri

• O alta functie predefinita este string=.

• Aceasta functie intoarce TRUE daca cele doua


stringuri date ca argumente sunt alcatuite din
aceleasi caractere si FALSE, in caz contrar.
7

Exemple
8

Stringuri
• Pentru a accesa un anumit caracter in string, se
utilizeaza formularea (char string index).

• char este predefinit, string reprezinta sirul dorit


iar index pozitia caracterului care va fi intors.

• Index-ul primului caracter din string e 0.

• Index-ul nu trebuie sa depaseasca lungimea


sirului.
9

Exemple
10

Caractere

• Se poate observa ca un caracter este scris de


catre Lisp cu prefixul #\.

• Tot in acelasi mod va da si utilizatorul


caracterele.

• Un caracter este evaluat in el insusi.


11

Exemple
12

Caractere

• Pentru testarea faptului ca doua caractere sunt


identice, se foloseste functia char=.

• La fel ca la testarea egalitatii pentru numere, dar


diferit de aceeasi testare pentru stringuri,
aceasta functie poate lua orice numar de
argumente.
13

Exemple
14

Stringuri si caractere

• Pentru a utiliza simbolul “ ca parte a unui string,


va trebui sa folosim caracterul \.

• Pentru a utiliza caracterul \ apoi ca parte a unui


string, trebuie sa mai adaugam inca unul in fata
sa.
15

Exemple

• Observati ca lungimea stringurilor nu este


influentata de caracterul \.
16

Exemple
• Pentru testarea egalitatii a doua stringuri, a
doua poate fi si caracter.
• Pentru acelasi lucru in cazul caracterelor,
amandoua trebuie sa fie de acest fel.
17

Caracterele spatiu si ENTER


18

Simboluri
• Simbolurile sunt un alt tip de data in Lisp.

• Pentru a reprezenta un simbol, se folosesc


secvente de litere si caracterele * si -:
▫ De exemplu: paul, pi, *read-base*

• Un simbol poate reprezenta ceva pentru care


dorim sa stocam informatii:
▫ De exemplu, paul poate reprezenta o persoana.
19

Simboluri
• Simbolurile sunt de asemenea folosite drept
variabile.

• Astfel, un simbol poate avea o valoare: se spune


ca este legat, sau este, dimpotriva, nelegat (fara
valoare).

• Unele simboluri legate sunt: pi, *read-base*,


*print-base* si *package*.
20

Exemple

• pi reprezinta valoarea simbolului matematic.


• *read-base* si *print-base* specifica in ce baza
vor fi scrise numerele de catre utilizator,
respectiv de Lisp.
• *package* specifica pachetul in care ne aflam
curent.
21

Simboluri
• Cele mai importante simboluri in Lisp sunt T si
NIL.

• T reprezinta true, iar NIL desemneaza false si


lista vida.

• Aceste simboluri sunt legate chiar la ele insele.


22

Simboluri

• Atunci cand vrem sa utilizam un simbol si nu


valoarea sa, punem ‘ in fata acestuia.
23

Simboluri
• Putem scrie simbolurile cu litere mici, Lisp le
converteste la litere mari.
24

Simboluri
• Pentru a testa egalitatea dintre doua simboluri,
se foloseste functia eql.
25

Functia eql

• Aceasta functie e mai generala chiar, testand


daca sunt identice oricare doua obiecte Lisp:
▫ Simboluri
▫ Caractere
▫ Numere de acelasi tip
26

Exemple
27

Simboluri

• Orice simbol are un nume reprezentat de un


string.

• Putem afla acest nume utilizand functia symbol-


name.
28

Exemplu
29

Simboluri

• Daca se doreste ca un caracter sa ramana scris cu


litera mica in cadrul unui simbol, se va folosi \.

• Daca vrem ca Lisp sa pastreze literele exact cum


le dam, le vom scrie intre ||.
30

Exemplu

• Pentru a scrie un simbol, Lisp foloseste si ||.

• Acestea nu fac parte din simbol sau din numele


sau.
31

Exemplu

• Simbolurile cu litere diferite ca marime sunt la


randul lor diferite.
32

Mai multe exemple


33

Tipul unui obiect


• Pentru a afla care este tipul unui anume obiect,
se foloseste functia type-of.
34

Mai multe exemple


35

Pachete

• Fiecare multime de simboluri este pastrata intr-


un pachet Common Lisp.

• Utilizatorul isi poate crea propriul pachet si il


poate exporta pentru ca altii sa il poata utiliza.

• Un pachet poate fi evident importat in alt pachet.


36

Pachete

• Am vazut ca un simbol poate avea diverse


reprezentari si totusi sa ramana acelasi simbol,
cu acelasi nume.

• In continuare vom vedea ca simboluri diferite


pot avea acelasi nume daca sunt in pachete
diferite.
37

Pachete
• Atunci cand interactionam cu Lisp, ne aflam deja
intr-un pachet.

• Putem vedea pachetul curent verificand valoarea


simbolului *package*.
38

Functia describe
• Prin apelul acestei functii Lisp putem afla
diverse proprietati despre obiecte.

• Printre altele, putem vedea pachetul din care fac


parte diferite simboluri.
39

Exemple
40

Exemple
41

Pachete

• Ne putem muta in alt pachet apeland functia in-


package.

• Acolo putem referi simboluri existente sau unele


noi create de utilizator.
42

Exemplu
43

Pachete
• Pentru a referi un acelasi simbol din alt pachet,
folosim exprimarea:
nume_pachet::nume_simbol

• Cele doua simboluri sunt diferite.


44

Pachete
• Sa ne intoarcem acum la pachetul common-lisp-
user si sa aflam informatii despre simbolul ‘paul.
45

Pachete
• Cele doua simboluri din pachete diferite nu sunt
identice decat ca nume.
46

Pachete
• Un simbol poate fi exportat din pachetul sau prin
apelarea functiei export.

• Numele unui simbol extern este de forma:

nume_pachet:nume_simbol
47

Exemplu
48

Pachete
• Pentru a afla daca un simbol a fost exportat sau
este inca intern intr-un anumit pachet, se poate
proceda precum in cele ce urmeaza.
49

Pachete
• Putem de asemenea defini pachete noi.
50

Pachete
• Daca dorim sa importam un simbol extern
dintr-un pachet in altul folosim functia import.
51

Alte observatii

• Atunci cand referim prima data un simbol, Lisp


il si construieste; deci, un simbol nou nu trebuie
declarat inainte.

• Daca vom incerca sa suprascriem un simbol care


deja exista intr-un pachet, vom primi mesaj de
eroare.
52

Exemplu
53

Alte observatii
• Simbolurile standard din pachetul lisp sunt
externe si importate automat in alte pachete.
54

Pachetele ca tip de data


• Ca tip de data in Lisp, putem afla mai multe
informatii despre pachetul curent.
55

Pachetele ca tip de data


• Pe langa functiile deja cunoscute, find-package ne
spune pachetul al carui nume prescurtat il stim.
56

Procesarea de baza a listelor


• Pana acum am discutat despre evaluarea S-
expresiilor care erau date sub forma de liste.

• In continuare, vom discuta despre liste ca tip de


baza in Lisp.

• Pentru a crea o lista, se foloseste functia de baza:


(cons obiect lista), unde:
▫ primul argument poate fi orice obiect Lisp
▫ al doilea este o lista
▫ intoarce o lista cu primul argument inserat ca prim
membru si restul listei fiind al doilea argument
57

Exemplu
58

Primul element si restul listei


• Pentru a accesa primul element al unei liste si
lista ramasa, se folosesc predicatele
(first list) si (rest list)
59

Mai multe exemple


60

Procesarea listelor
• Functia equal spune daca elementele a doua
liste sunt egale doua cate doua sau nu.
61

Functia de determinare a lungimii unei


liste
62

Alte observatii
• Dupa ce am tastat o forma in Lisp, o putem
imediat reapela cu *; cu ** putem reapela
penultima forma.
63

Pe saptamana viitoare…
Programare in Lisp pur

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2

Definirea propriilor functii


• “Un program Lisp este o colectie de functii scrise
de un programator Lisp” - S. C. Shapiro.

• Pentru a defini o functie, se foloseste forma


defun:

(defun functie lista_variabile string_documentie forma)

▫ functie este un simbol;


▫ lista_variabile este o lista de simboluri;
▫ string_documentatie este un string;
▫ forma este o forma Lisp.
3

Definirea propriilor functii


(defun functie lista_variabile string_documentie forma)
▫ defun intoarce functie;
▫ defineste functie ca fiind numele unei functii;
▫ argumentele (atributele) sale formale sunt simbolurile din
lista_variabile;
▫ definitia sa se afla in forma;
▫ documentatia (explicatiile) pentru aceasta functie sunt in
string_documentatie.

• Observatie: Argumentele formale ale functiei poarta numele de


variabile lambda.

• Numele provine de la calculul lambda al lui A. Church care sta la


baza Lisp-ului.
4

Exemplu

Simbol

• Se defineste functia lista.


• Aceasta ia trei obiecte Lisp ca atribute actuale.
• Intoarce o lista care are ca membri cele trei obiecte
Lisp date ca argumente.
• Dupa ce functia este evaluata, aceasta se poate
folosi ca si cum ar fi una predefinita in Lisp.
• Putem afla de asemenea informatii despre functie
cu expresia documentation.
5

Apelarea unei functii

• Cand o functie este apelata, se parcurg urmatorii pasi:


▫ Lisp verifica daca primul membru al listei este un simbol care reprezinta
o functie;
▫ Obiectele date ca argumente sunt evaluate;
▫ Valorile obiectelor devin valorile atributelor formale.
▫ Variabilele formale sunt legate la valorile date;
▫ Forma care reprezinta definitia functiei este evaluata;
▫ Atributele formale sunt dezlegate;
▫ Valoarea formei de definitie este intoarsa.
6

Apelarea unei functii - exemplu


• Cand o functie este apelata, se • Cand functia definita este
parcurg urmatorii pasi: apelata, avem urmatorii pasi:
▫ Lisp verifica daca primul
membru al listei este un simbol ▫ lista este un simbol care
care reprezinta o functie; reprezinta o functie;
▫ Obiectele date ca argumente sunt ▫ ‘a este evaluat drept A, (cons ‘b
evaluate; ‘()) ca (B) si ‘c drept C;
▫ Valorile obiectelor devin valorile
atributelor formale, adica ▫ o1 este legat la A, o2 la (B) si o3
variabilele formale sunt legate la la C;
valorile date; ▫ (cons o1 ( cons o2 (cons o3 ‘())))
▫ Forma care reprezinta definitia este evaluata, fiecare obiect
functiei este evaluata;
avand valorile de mai sus;
▫ Atributele formale sunt ▫ o1, o2 si o3 revin la valorile
dezlegate; initiale;
▫ Valoarea formei de definitie este ▫ Se intoarce (A (B) C).
intoarsa.
7

Reversul unei liste de doua numere


• Folosind functiile predefinite first si second care
dau primul si cel de-al doilea element al unei
liste, sa se defineasca o functie care inverseaza cei
doi membri ai unei liste.
8

Patratul unui numar

• Definiti o functie care sa calculeze patratul unui


numar dat n.
9

Reversul unei liste de trei numere

• Folosind functia predefinita third care da


elementul de pe pozitia a treia dintr-o lista, sa se
defineasca o functie care inverseaza cei trei
membri ai unei liste.
10

Definirea de functii in pachete

• Sa definim un nou pachet pentru a defini


functiile personale.

• Ne mutam din pachetul curent in cel nou definit.

• Sa definim, de exemplu, pachetul invatare.

• In interiorul sau sa definim functia fn.


11

Definirea de functii in pachete

• Apelam functia describe pentru a vedea daca fn


este un simbol mostenit din alt pachet.

• Daca acesta este cazul, apelam functia shadow


avand ca argument functia existenta.

• Reapelam describe pentru a fi siguri ca fn este


acum simbol al pachetului invatare.
12

Definirea de functii in pachete


• Definim functia fn.

• Testam functia nou definita.

• Verificam daca functia fn originala se poate inca


folosi, utilizand formularea ce include pachetul
care o exporta.

• Exportam simbolul functional nou definit, daca


dorim folosirea sa si in alte pachete.
13

Redefinirea functiei predefinite last


• last intoarce o lista formata din ultimul element
dintr-o lista data.
• In versiunea noastra, va intoarce al treilea
element al unei liste date.
14

Continuare
15

Continuare
16

Alt exemplu
• Sa definim o functie care sa recunoasca semnul
intrebarii, ?.
17

Problema!

• Cand am definit functia, ne aflam in pachetul


invatare, in care argumentul sau era invatare::?.
• Cand il testam in pachetul common-lisp-user,
argumentul va fi common-lisp-user::?.
• Cele doua simboluri sunt evident diferite.
18

Reformulare
• Intentionam de fapt nu sa recunoastem simbolul
?, ci sa recunoastem orice simbol al carui nume
de afisare este ?.
19

Salvarea definitiilor intr-un fisier


• Pentru a putea salva functiile definite pentru o
reapelare urmatoare, le vom stoca intr-un fisier
cu extensia *.lisp.

• Acest fisier il putem crea in Notepad, avand grija


ca, in momentul salvarii sa alegem optiunea All
Files.

• Fisierul se salveaza in directorul in care avem


instalat Lisp-ul.
20

Compilarea si incarcarea
definitiilor
• Pentru a vedea eventualele erori/atentionari,
vom compila fisierul rezultat, prin apelarea
functiei (compile-file “nume.lisp”).

• Pentru incarcarea in memoria Lisp, se foloseste


apelarea (load “nume”).

• Se apeleaza apoi functia definita in modul clasic


de lucru cu Lisp.
21

Exemplu – Suma a trei numere


22

Inversarea unei liste de 4 membri


23

Calculul discriminantului
• Presupunem ca avem o ecuatie de gradul 2 fara
radacini complexe.
• Testul complet al tuturor posibilitatilor va
constitui o parte a cursului viitor.
24

Calculul radacinilor ecuatiei de gradul 2


• Presupunem ca ecuatia nu are radacini complexe
si ca a este diferit de 0.
25

Functii predicat
• Acestea sunt functiile care intorc fie True (T), fie
False (NIL).

• Pana acum, am intalnit exemple de astfel de


functii, =, char=, string=, eql, equal.

• O multime standard de functii predicat sunt cele


care verifica tipul obiectelor Lisp.

• O astfel de functie intoarce T daca tipul


obiectului este cel specificat si NIL, altfel.
26

Exemple
27

Combinarea functiilor predicat


• Pentru a alatura rezultatele functiilor predicat,
Lisp utilizeaza operatorii logici and si or.

• Acesti operatori lucreaza cu un numar arbitrar de


elemente.

• Fiecare se opreste atunci cand intalneste primul


rezultat al unui predicat care deja conduce la
raspunsul final:
▫ Un T in cazul unei disjunctii;
▫ Un NIL in cazul unei conjuctii.
28

Testarea raportului a doua numere


29

Compararea a doua numere


30

Lungimea unui string / a unei liste


31

Pe saptamana viitoare…
Expresii conditionale

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
2

Conditionalul
• Lisp are doua tipuri de expresii conditionale:
▫ IF
▫ COND

• Functia care exprima clasicul IF are formularea


(if test then else):
▫ if e cuvint cheie.
▫ Daca test e adevarat, atunci se intoarce valoarea
lui then; altfel, vom obtine valoarea lui else.
3

Exemple simple
4

Exemple – Testare raport


5

Exemple – Modulul unui numar


6

Exemplu – Functia semn


7

Conditionalul COND
• Expresia IF este potrivita pentru a alege intre
doua calcule pe baza unui singur test.

• Insa, in momentul in care avem de ales intre


teste multiple, folosirea lui IF este greoaie si
greselile pot aparea foarte usor.

• In aceste cazuri, vom utiliza alternativa lui IF si


anume conditionalul COND.

• Evident, in cazul invers, cand avem un singur


test, este mai eficient sa folosim IF.
8

Conditionalul COND
• Functia COND are sintaxa (cond (p1 e1) … (pn
en)):
▫ Evalueaza pi-urile in ordine pana cand unul dintre
ele, pj, este true.
▫ Atunci intoarce ej.
▫ Daca niciun pi nu este evaluat ca True, atunci
intoarce False.

• Fiecare lista (pi ei) poarta numele de pereche


COND:
▫ pi este testul (conditia).
▫ ei este expresia.
9

Exemplu – Functia semn - Reluare


10

Asemanare cu IF-ul procedural

if p1 then e1 (cond (p1 e1)


else if p2 then e2 (p2 e2)
else if p3 then e3 (p3 e3)
else e4 (t e4))
11

Recursivitate
12

Folosirea functiilor recursive


• Sa calculam recursiv suma a doua numere
nenegative.
13

Observarea recursivitatii
14

Definirea unei functii recursive


• Fiecare functie recursiva poate avea formularea:
▫ (defun functie lista_variabile (cond
perechi_cond))
▫ sau (defun functie lista_variabile (if test then
else)).

• In cazul unei functii recursive corect definite, un


apel cu parametri nepotriviti poate genera o
recursivitate infinita.
15

Functia ASSERT
• Pentru a evita argumente gresite, atunci cand
definim o functie putem folosi constructia assert.
• Sintaxa acesteia este:
(assert asertie (variabile_de_schimbat) string
variabile_mentionate)
▫ Asertia este evaluata.
▫ Daca este True, functia se executa normal.
16

Functia ASSERT
(assert asertie (variabile_de_schimbat) string
variabile_mentionate)
▫ Daca este False, Lisp printeaza o eroare:
 Ii da utilizatorului optiunea de a termina sau de a
schimba valorile acelor variabile_de_schimbat.
 Mesajul din string este afisat.
 In acest string putem mentiona anumite variabile,
scriind ~S pentru fiecare si trecandu-le in cadrul
campului variabile_mentionate.
17

Redefinim suma a doua numere


18
19

O alta versiune a sumei


20

Produsul a doi intregi nenegativi


21

Produsul a doi intregi nenegativi


22

Produsul a doi intregi nenegativi


23

Ridicarea unui numar la putere


24

Recursivitatea la liste
• Ca prim exemplu, sa incercam definirea versiunii
proprii a functiei length, care determina
lungimea unei liste.

• Partea recursiva: Lungimea unei liste nevide


este cu o unitate mai mare decat lungimea
restului listei.

• Conditia de terminare: Lungimea listei vide


() este 0.
25

Lungimea unei liste

(
26

Apartenenta unui element la o lista


27

Testarea daca o lista e formata sau nu


numai din numere
28

Testarea daca o lista e formata sau nu


numai din numere – alta versiune
29

Verificarea egalitatii lungimii a doua


liste
30

Verificarea egalitatii lungimii a doua


liste - varianta
31

Pe saptamana viitoare…
Recursivitate – continuare

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
Testarea daca e1 se afla inaintea lui e2
in lista l
Numarul aparitiilor unui element intr-o
lista
Testarea egalitatii elementelor a doua
liste
Elementul de pe pozitia n din lista l
Obtinerea listei elementelor de dupa
pozitia n
Construirea copiei unei liste
Concatenarea a doua liste
Inversarea unei liste

Transforma un
element simplu in
lista ce contine
acel element.
Inversa – alta solutie
Substituirea primei aparitii a unui
element dintr-o lista cu un element
nou
Transformarea din lista in multime
Reuniunea a doua multimi
Lista cu primele n elemente din lista
data ca argument
Inserarea unui element intr-o multime
Intersectia a doua multimi
Diferenta a doua multimi
Verificarea daca o multime e
submultime a unei alte multimi
Egalitatea a doua multimi – fara a lua
in considerare ordinea elementelor
Produsul cartezian a doua multimi
Vom defini o functie care
cupleaza un element dat
cu fiecare membru al unei
liste, rezultand o lista a
cuplurilor formate.
Cuplarea unui element cu fiecare
membru al unei liste
Prefixul unei liste
Sufixul unei liste
Schimbul intre 2 elemente dintr-o
lista
Pozitia unui element intr-o lista

La apelare, contorul se
initializeaza cu valoarea 1.
Adunarea succesiva a cate doua
elemente dintr-o lista
n− 1
∑ x [ i] x [i 1]
i= 1
Impartirea unei liste in doua liste: prima cu
elemente pare, cea de-a doua cu impare
Impartirea unei liste in doua liste: prima cu
elementele de pe pozitiile pare, cea de-a
doua cu cele de pe pozitiile impare
Determinarea tuturor numerelor pana
la un numar n dat, care sunt divizibile
cu un numar k
Maximul unei liste – Metoda 1
Maximul unei liste – Metoda 2
Maximul unei liste – Metoda 3
Quicksort – sortarea unei liste
Pe saptamana viitoare…
Prolog vs. Lisp prin Exemple

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
Numarul elementelor dintr-o lista

• Dacă lista este vidă, numarul elementelor sale


este zero: aceasta este condiţia de oprire a
recursivităţii.

• În clauza recursiva, primul element din listă nu


ne interesează, vrem doar să îl eliminăm ca să
numărăm câte elemente are lista rămasă.

• Numărul curent va fi, de fiecare data, egal cu 1


plus numărul elementelor din lista rămasă.
Numarul elementelor dintr-o lista
PROLOG

nr_elem([], 0).
nr_elem([_ | Rest], N) :- nr_elem(Rest, N1), N is N1 + 1.

?- nr_elem([1, 2, 3], X).

X=3
LISP

(defun lungime(l)
(if (null l) 0 (+ 1 (lungime (rest l)))
)
)

>(lungime ‘(1 5 6 4))


4
Suma elementelor dintr-o lista
• Dacă lista este vidă, suma elementelor sale este
zero: aceasta este condiţia de oprire a
recursivităţii.

• În clauza recursiva, primul element din listă ne


interesează de data aceasta, dupa care
calculam suma elementelor din lista rămasă.

• Suma curentă va fi, de fiecare data, egală cu


elementul curent plus suma elementelor din lista
rămasă.
Suma elementelor dintr-o lista
PROLOG

suma([], 0).
suma([P|Rest], S) :- suma(Rest, S1), S is S1 + P.

?- suma([1, 2, 3], X).

X=6

LISP

(defun suma (l)


(if (null l) 0 (+ (first l) (suma (rest l)))
)
)
>(suma ‘(1 5 6 4))
16
Media elementelor unei liste
• Media unei liste se calculeaza drept suma
elementelor din lista / numarul acestora.
PROLOG
LISP
media(L) :- nr_elem(L, N), suma(L, S), (load “suma”)
Media is S/N, write('Media este '), (load “lungime”)
write(Media). (defun media (l)
(/ (suma l) (lungime l))
?- media([1, 2, 3]). )

Media este 2. >(media ‘(1 5 6 4))


4
7/56

Apartenenta unui element la o lista

• Vom defini predicatul apartine/2, unde primul


argument reprezintă elementul pentru care
verificăm apartenenţa, iar al doilea este lista.

• X aparţine listei dacă este capul listei sau dacă


aparţine coadei acesteia.

12:15 PM
8/56

Apartenenta unui element la o lista


PROLOG LISP

apartine(X, [X | _]). (defun membru (n l)


(cond ((null l) nil)
apartine(X, [Y | Rest]) :- apartine(X, Rest).
((eql n (first l)) t)
(t (membru n (rest l)))
?- apartine (3, [1, 3, 2]). )
Yes )

?- apartine (4, [1, 3, 2]). >(membru 3 ‘(1 4 3 5 6))


No T

>(membru 3 ‘(1 5 6 8))


NIL

12:15 PM
9/56

Inversarea unei liste


• Pe langa lista initiala si lista in care depunem
rezultatul, se considera si o lista temporara care
este initial vida.

• Capul listei curente se adauga la inceputul listei


temporare – acesta era initial goala, deci
elementele se vor adauga in ordine inversa.

• Cand lista care trebuie inversata devine vida,


unificam lista finala cu cea temporara.

12:15 PM
Inversarea unei liste
PROLOG

inv(L, Linv) :- inv1(L, [], Linv).


inv1([], L, L).
inv1([X|Rest], Temp, L) :- inv1(Rest, [X|Temp], L).
LISP
?- inv([1, 2, 3], L).
L = [3, 2, 1] (defun inversa (l)
(inv l '())
)

(defun inv(l1 l2)


(if (null l1) l2 (inv (rest l1) (cons (first l1) l2))
)
)

>(inversa ‘(1 2 3 4))


(4 3 2 1)
Pozitia i dintr-o lista
• Enuntul problemei:
▫ Dându-se o listă şi un număr întreg pozitiv i, să se
găsească elementul aflat pe poziţia i în listă.
• Avem doua argumente de intrare, o lista si un
numar care da pozitia care ne intereseaza.

• Cum rezolvam problema: scadem i-ul cu cate o


unitate si, in acelasi timp, scoatem cate un
element din lista. Cand i-ul este 1, primul element
din lista este cel cautat.
Pozitia i dintr-o lista
PROLOG

pozi([X|_], 1, X).
pozi([_A|R], I, X) :- I1 is I - 1, pozi(R, I1, X).

? - pozi([mere, portocale, pere, gutui], 2, Ce).


Ce = portocale

LISP

(defun elemi(i l)
(if (= n 1) (first l) (elemi (- n 1) (rest l))
)
)

>(elemi 3 ‘(1 4 5 6))


5
13/56

1 4 6 7 8 9 0 3 2 4 5 6 7

Pozitia unui element intr-o lista

• Enunt problema:
▫ Având date o listă şi un element care aparţine
acestei liste, să se specifice pe ce poziţie este situat
elementul în lista dată.
• Avem doua argumente de intrare:
▫ Lista in care se gaseste elementul
▫ Elementul pentru care trebuie sa gasim pozitia
• Vom mai construi un predicat care sa contina si
o variabila contor care este initial 1.
14/56

1 4 6 7 8 9 0 3 2 4 5 6 7

Pozitia unui element intr-o lista


PROLOG
pozx(L, X, P):- pozx(L, X, 1, P).
pozx([X|_], X, P, P).
pozx([_|R], X, C, P) :- C1 is C + 1, pozx(R, X, C1, P).
? – pozx([ion, petre, marin, olivia], marin, P).
P=3

LISP
(defun pozitia (l el p)
(if (eql el (first l)) p (pozitia (rest l) el (+ p 1))))

(defun poz (l el)


(poz l el 1))

>(poz ‘(a b c d e) ‘d 1)
4
15/56

Stergerea aparitiilor unui element


dintr-o lista
• Enunt problema:
▫ Să se şteargă toate apariţiile unui element dintr-o
listă.
• Avem doua argumente de intrare:
▫ Lista din care se vor sterge aparitiile unui element
▫ Elementul care trebuie sters
• Argumentul de iesire va fi noua lista care
nu va mai contine elementul dat.
16/56

Stergerea aparitiilor unui element


dintr-o lista
PROLOG
sterg([], _, []).
sterg([N|Rest], N, Rez) :- sterg(Rest, N, Rez).
sterg([M|Rest], N, [M|Rez]) :- sterg(Rest, N, Rez).
? – sterg([1, 4, 6, 8, 6, 12, 6], 6, L).
L = [1, 4, 8, 12]

LISP
(defun sterg (l el)
(cond ((null l) ‘())
((eql (first l) el) (sterg (rest l) el))
(t (cons (first l) (sterg (rest l) el)))
)
>(sterg ‘(1 4 6 8 6 12 6) 6)
(1 4 8 12)
Eliminarea duplicatelor dintr-o
lista
• Enunt problema:
▫ Să se realizeze eliminarea duplicatelor dintr-o listă
dată.
• Argument de intrare:
▫ O lista data
• Argument de iesire:
▫ Lista rezultata prin eliminarea duplicatelor din lista
data.
• Luam fiecare element din prima lista si verificam
daca apartine restului listei (adica daca mai apare in
lista).
▫ Daca nu mai apare, atunci il adaugam in lista rezultat
▫ Altfel, nu il adaugam.
12:15 PM
Eliminarea duplicatelor dintr-o
lista
PROLOG
duplicate([], []).
duplicate([X|R1], L) :- apartine(X, R1),
duplicate(R1, L).
duplicate([X|R1], [X|R2]) :- duplicate(R1, R2).

? – duplicate([7, 9, 7, 11, 11], L).


L = [9, 7, 11]
LISP

(defun duplicate(l)
(cond ((null l) '())
((member (first l) (rest l)) (duplicate (rest l)))
(t (cons (first l) (duplicate (rest l))))
)
))
>(duplicate ‘(7 9 7 11 11))
(9 7 11)
19/56

Maximul unei liste


• Consideram primul element al listei ca fiind maximul.

• Apelam un alt program ce are drept argumente lista


ramasa si elementul considerat.

• Parcurgem restul listei; daca gasim un element (capul


listei curente) mai mare decat maximul, acesta va deveni
noul maxim.

• Altfel, mergem mai departe in restul listei.

• Recursivitatea se incheie cand ajung la lista vida si se


intoarce argumentul corespunzator maximului.
20/56

Maximul unei liste


PROLOG
max([P|Rest]) :- Max = P, max1(Rest, Max, M).
max1([], Max, Max).
max1([P|R], Max, M) :- P > Max, max1(R, P, M); max1(R, Max, M).

?- max([4, 2, 5, 1]).
Maximul este 5. LISP
(defun maxim2 (l max)
(cond ((null l)
max)

((> (first l) max) (maxim2 (rest l) (first l)))

(t (maxim2 (rest l) max))))

(defun maxim1
(l)
21/56

Pozitia pe care se afla maximul unei


liste

• Consideram primul element al listei ca fiind maximul si


stabilim pozitia maximului drept 1.

• Apelam un alt predicat ce are drept argumente:


▫ lista ramasa
▫ elementul considerat drept maxim
▫ pozitia pe care se afla acesta
▫ si un contor care va numara elementele.
22/56

Pozitia pe care se afla maximul unei


liste
• Parcurgem lista; daca gasim un element (capul noii liste)
mai mare decat maximul:
▫ acesta va deveni noul maxim
▫ pozitia pe care se afla maximul devine contorul curent
▫ si se incrementeaza contorul.

• Altfel, mergem mai departe in restul listei, incrementand


contorul.

• Recursivitatea se incheie cand ajung la lista vida si


afisez argumentul corespunzator pozitiei pe care se afla
maximul.
23/56

Pozitia maximului unei liste


PROLOG
poz_max([P|Rest]) :- poz_max(Rest, P, 1, 1).

poz_max([], _, _, Poz) :- write('Maximul se gaseste pe pozitia '),


write(Poz).
poz_max([P|R], Max, Contor, Poz) :- Contor1 is Contor + 1, Max < P,
poz_max(R, P, Contor1,
Contor1).
poz_max([_|R], Max, Contor, Poz) :- Contor1 is Contor + 1,
poz_max(R, Max,
Contor1, Poz).

?- poz_max([4, 2, 5, 1]).
Maximul se gaseste pe pozitia 3
24/56

Pozitia maximului unei liste


LISP
(defun pozmax(l)
(pozm (rest l) (first l) 1 2)
)

(defun pozm (l m p c)
(cond ((null l) p)
((> (first l) m) (pozm (rest l) (first l) c (+ c 1)))
(t (pozm (rest l) m p (+ c 1)))
)
)
> (poz_max ‘(4 2 5 1)
3
25/56

Interclasarea a doua liste

• Ce presupune interclasarea?

• Avem doua liste care trebuie unite intr-una singura.

• Cele doua liste trebuie sa fie ordonate crescator.

• Elementele listei rezultate trebuie sa fie de asemenea in


ordine crescatoare.
26/56

Interclasarea a doua liste

• Capetele celor doua liste ce trebuie unite se compara.

• Cel mai mic dintre ele se va adauga la lista rezultat.

• Daca sunt egale, se adauga doar o data.

• Daca una dintre ele este vida, lista rezultat este cealalta.
27/56

Interclasarea a doua liste


PROLOG

interclasez([], L, L).
interclasez(L, [], L).
interclasez([P1|R1], [P2|R2], [P1|R3]) :- P1 < P2,
interclasez(R1, [P2|R2], R3).
interclasez([P1|R1], [P1|R2], [P1|R3]) :- interclasez(R1, R2, R3).
interclasez(R1, [P2|R2], [P2|R3]) :- interclasez(R1, R2, R3).

?- interclasez([1, 3, 7], [2, 3, 4, 8], L).


L = [1, 2, 3, 4, 7, 8]
28/56

Interclasarea a doua liste


LISP

(defun interclasez (l1 l2)


(cond ((null l1) l2)
((null l2) l1)
((< (first l1) (first l2)) (cons (first l1) (interclasez (rest l1) l2)))
((= (first l1) (first l2)) (cons (first l1) (interclasez (rest l1) (rest l2))))
(t (cons (first l2) (interclasez (rest l1) l2)))
)

> (interclasez ‘(1 3 7) ‘(2 3 4 8))


(1 2 3 4 7 8)
29/56

Prefixul unei liste

• Pentru a testa daca o lista e prefixul altei liste,


compar element cu element cele doua liste.

• Adica, verific daca elementul cap al unei liste prefix


este egal cu cel al listei complete.

• Daca raspunsul este afirmativ, merg mai departe.

• Prima lista e prefix a celei de-a doua daca, la un


moment dat, lista prefix se incheie.
30/56

Prefixul unei liste


PROLOG
prefix([], _L).
prefix([X|R1], [X|R2]) :- prefix(R1, R2).

?- prefix([1,2], [1, 2, 3]).


Yes

?- prefix([1,3], [1, 2,3]).


No LISP
(defun prefix (l1 l2)
(cond ((null l) t)
((eql (first l1) (first l2)) (prefix (rest l1) (rest l2)))
(t nil)))
>(prefix ‘(1 2) ‘(1 2 3)) t
>(prefix ‘(1 3) ‘(1 2 3)) nil
31/56

Sufixul unei liste


• Pentru a testa daca o lista e sufixul altei liste,
parcurg lista completa pana intalnesc exact lista
sufix.

• Adica, scot elementul cap al listei mari, pana cand


cele doua liste sunt egale.

• Recursivitatea se opreste deci cand cele doua


argumente sunt egale.
32/56

Sufixul unei liste


PROLOG
sufix(L, L).
sufix(L, [_Y|Rest]) :- sufix(L, Rest).

?- sufix([1,2,3],[1,2]).
No
?- sufix([1, 2, 3], [3]).
Yes LISP
(defun sufix (l1 l2)
(cond ((null l2) nil)
((equal l1 l2) t)
(t (sufix l1 (rest l2)))))

>(sufix ‘(2 3) ‘(1 2 3)) t

>(sufix ‘(1 3) ‘(1 2 3)) nil


Numere pare, numere impare

• Enunt problema:

▫ Se dă o listă: să se obţină două liste din aceasta


astfel încât prima din ele să conţină elementele
pare iar a doua pe cele impare.

• Vom avea asadar o singura lista ca argument de


intrare si doua liste ca argumente de iesire.
Numere pare, numere impare
PROLOG

pareimpare([], [], []).


pareimpare([X|Rest], [X|R1], L2):-X1 is X mod 2, X1=0,
pareimpare(Rest, R1, L2).
pareimpare([X|Rest], L1, [X|R2]):-pareimpare(Rest, L1, R2).

?- pareimpare([1, 2, 3, 4, 5, 6], L1, L2).


L1=[2, 4 , 6]
L2=[1, 3, 5]
Numere pare, numere impare
LISP
(defun pare (l)
(cond ((null l) '())
((= (mod (first l) 2) 0) (cons (first l) (pare (rest l))))
(t (pare (rest l)))))

(defun impare (l)


(cond ((null l) '())
((/= (mod (first l) 2) 0) (cons (first l) (impare (rest l))))
(t (impare (rest l)))))

(defun pareimpare (l)


(cons (pare l) (cons (impare l) '())))

>(pareimpare ‘(1 2 3 4 5 6))


((2 4 6) (1 3 5))
Pozitii pare, pozitii impare

• Enunt problema:
▫ Se dă o listă: să se obţină două liste din aceasta astfel
încât prima din ele să conţină elementele de pe
poziţiile pare iar a doua pe cele de pe poziţiile impare.

• Vom avea asadar o singura lista ca argument de


intrare si doua liste ca argumente de iesire.
Pozitii pare, pozitii impare
PROLOG
parimpar([X], [], [X]).
parimpar([X, Y],[Y], [X]).
parimpar([X, Y|R], [Y|R1], [X|R2]) :- parimpar(R, R1, R2).
? – pare([ion, marius, mananca, invata, mere, prolog], P, I).
P = [marius, invata, prolog]
I = [ion, mananca, mere]
LISP
(defun impar(l)
(if (null l) '() (cons (first l) (impar (rest (rest l))))
))
(defun par(l)
(if (null l) '() (cons (second l) (par (rest (rest l))))
))
(defun parimpar(l)
(cons (impar l) (cons (par l) '()))
)
>(parimpar ‘(1 5 6))
((1 6) (5))
38/41

Ordonarea unui sir de numere

• Având în fişierul de intrare in.txt câte un număr urmat de


caracterul punct pe fiecare linie, construiţi un predicat
Prolog care să scrie în fişierul ordonat.txt şirul de numere
ordonat crescător.
▫ Citim numerele din in.txt intr-o lista, le ordonam in alta lista
si le scriem apoi in fisierul ordonat.txt.
▫ O sa facem in continuare numai ordonarea elementelor unei
liste.
▫ Pentru aceasta, vom folosi metoda quicksort care utilizeaza
mecanismul divide et impera.
39/41

Ordonarea elementelor unei liste

PROLOG
sortez([], []).
sortez([P|Rest], Lrez):- selectez(P, Rest, Mici, Mari), sortez(Mici,
MiciSort), sortez(Mari, MariSort), append(MiciSort, [P|MariSort],
Lrez).

selectez(_, [], [], []).


selectez(P, [P1|Rest], [P1|Mici], Mari):- P1 < P, selectez(P, Rest, Mici,
Mari).
selectez(P, [P1|Rest], Mici, [P1|Mari]):- selectez(P, Rest, Mici, Mari).

?-sortez([2, 4, 5, 3, 1], L).


L=[1, 2, 3, 4, 5]
40/41

Ordonarea elementelor unei liste


LISP
(defun sortez (l)
(if (null l) '()
(append (sortez (selectMici (first l) (rest l))) (list (first l)) (sortez
(selectMari (first l) (rest l))))))

(defun selectMari (el l)


(cond ((null l) '())
((< el (first l)) (cons (first l) (selectMari el (rest l))))
(t (selectMari el (rest l)))))

(defun selectMici (el l)


(cond ((null l) '())
((> el (first l)) (cons (first l) (selectMici el (rest l))))
(t (selectMici el (rest l)))))
>(sortez ‘(1 4 5 3 2))
(1 2 3 4 5)
Pana saptamana viitoare…
Prolog & Lisp vs. Java.
Hill-climbing

Ruxandra Stoean
http://inf.ucv.ro/~rstoean
ruxandra.stoean@inf.ucv.ro
Avantajele programelor PNP

Sunt orientate catre implementari logice.


Sunt extrem de potrivite pentru sistemele expert
apartinand domeniului inteligentei artificiale.
Sunt usor de analizat, transformat, verificat,
intretinut.
In general, sunt eficiente si competitive in
comparatie cu programele nedeclarative.
Programul este mai degraba “intrebat” decat
executat.
Dezavantajele
programelor PNP
Nu sunt usor implementabile si utilizabile pentru
algoritmii matematici de cautare si optimizare din
cadrul inteligentei artificiale.

Nu sunt cele mai potrivite pentru exprimarea


algoritmilor folositi in industrie.

Au mecanisme mai putin directe decat ale celor


nedeclarative si sunt considerate de multe ori mai
“neprietenoase”.
O privire de ansamblu
• Cursul prezent incearca formularea unei
concluzii finale asupra:
▫ Diferentelor intre programarea procedurala si cea
nonprocedurala
▫ Avantajelor si dezavantajelor fiecareia
• Se doreste asadar:
▫ Rezolvarea unei probleme reale
▫ Utilizarea unui algoritm clasic de inteligenta
artificiala
▫ Comparatia intre implementarile in Prolog, Lisp
(programare nonprocedurala) si Java (programare
procedurala)
Hill-climbing
• Este ca si cand ai urca
un munte, este ceata
foarte deasa si ai avea
amnezie.
• Este vorba de o miscare
continua inspre valori
mai bune, mai mari (de
aici, urcusul pe munte).
• Algoritmul nu mentine
un arbore de cautare,
prin urmare, pentru
fiecare nod se retine
numai starea pe care o
reprezinta si evaluarea
sa.
Hill-climbing B

functia hill_climbing(problema) intoarce o solutie


Se pastreaza la fiecare reapelare: nodul curent si nodul urmator.

curent = genereaza_nod(stare_initiala[problema])
Cat timp este posibil executa
urmator = un succesor al nodului curent
Daca eval(urmator) < eval(curent) atunci
intoarce curent
curent = urmator
Sfarsit cat timp

6/17
Dezavantaje hill-climbing
• Maxime locale: este vorba de un varf care este
mai mic decat cel mai inalt varf din spatiul
starilor. Cand se ajunge la maxime locale,
algoritmul se opreste pentru ca nu mai poate
cobori dealul.
▫ Solutia gasita poate fi foarte slaba!

7/17
Problema comis-voiajorului
• Problema:
▫ Se dau n oraşe
▫ Să se găsească un tur
complet de lungime
minimală
• Reprezentare:
▫ Etichetăm oraşele 1, 2, … , n
▫ Un tur complet este o
permutare (pt. n =4:
[1,2,3,4], [3,4,2,1])
• Spaţiul de căutare este imens:
pentru 30 de oraşe sunt 30! 
1032 tururi posibile!
Implementarea in Prolog
dist(1,2,10). dist(3,4,5).
• Inregistrarea oraselor dist(1,3,25). dist(3,5,20).
si a distantelor dintre dist(1,4,30). dist(3,6,25).
acestea se va realiza dist(1,5,15). dist(3,7,7).
prin adaugarea de dist(1,6,30). dist(4,5,30).
dist(1,7,40). dist(4,6,15).
fapte si formularea dist(2,3,20). dist(4,7,20).
comutativitatii. dist(2,4,10). dist(5,6,17).
dist(2,5,25). dist(5,7,10).
dist(2,6,35). dist(6,7,10).
d(A,B,X):-dist(A,B,X). dist(2,7,45). orase(7).
d(A,B,X):-dist(B,A,X).
Predicatul principal
• Apeleaza algoritmul de hill-climbing si afiseaza drumul
optim si costul sau.
• Se specifica de asemenea configuratia principala si
numarul de iteratii (considerat drept conditie de oprire).

go:-start(Drum), hill_climber(Drum, 0, DrumOpt),


writeln('Drumul optim este'), writeln(DrumOpt),
write('Costul sau este '), eval(DrumOpt, FOpt),
writeln(FOpt).

start([1, 2, 3, 4, 5, 6, 7]).
iteratii(200).
Algoritmul de hill-climbing
• Se implementeaza functia definita in algoritm.
• Daca nu s-a ajuns la numarul de iteratii maxim, se
incearca “urcarea” intr-o configuratie succesoare.
• Daca se va reusi, nodul curent devine cel nou gasit si se
reapeleaza predicatul recursiv.

hill_climber(Drum, GenAnt, Drum):-iteratii(No), GenAnt


== No.
hill_climber(Drum, GenAnt, DrumOpt):-iteratii(No),
GenAnt \= No, eval(Drum, F), climb(Drum, F,
DrumNou),
GenNou is GenAnt + 1, hill_climber(DrumNou, GenNou,
DrumOpt).
Predicatul de urcare
• Se cauta un succesor al nodului curent.
• Daca evaluarea acestuia este mai buna decat a
celui curent, nodul curent devine cel nou generat.
• Altfel, nodul curent ramane neschimbat.

climb(Drum, F, DrumNou):-mutatie(Drum,
Drum1), eval(Drum1, FNou), FNou < F,
DrumNou = Drum1.
climb(Drum, _, Drum).
Evaluarea unei configuratii
• Evaluarea presupune costul drumului asociat
unei configuratii, adica suma distantelor dintre
orasele din circuit.

eval([X|Rest], F):-eval1([X|Rest], F1),


ultim([X|Rest], U), d(U, X, D), F is F1 + D.

eval1([_], 0).
eval1([X,Y|Rest], F):-d(X,Y,C), eval1([Y|Rest], F1),
F is F1 + C.
ultim([X], X).
ultim([_|Rest], X):-ultim(Rest, X).
Mutarea intr-un nod succesor
• Presupune, de fapt, aplicarea unui operator de mutatie
asupra configuratiei curente.
• Se genereaza aleator doua pozitii si valorile
corespunzatoare sunt interschimbate.

mutatie(Drum, Drum1):-orase(N), genereaza(N, Poz1,


Poz2), cauta(Drum, Poz1, X),
cauta(Drum, Poz2, Y), schimba(Drum, X, Y, Drum1).

genereaza(N, Poz1, Poz2):- repeat, P1 is random(N) + 1, P2


is random(N) + 1, P1 \= P2, !, Poz1 = P1, Poz2 = P2.
Interschimbare
• Se cauta in configuratia curenta care sunt valorile de pe
cele doua pozitii generate aleator.
• Cele doua valori se schimba intre ele.

cauta([X|_], 1, X).
cauta([_|Rest], Poz, El):-Poz1 is Poz - 1, cauta(Rest, Poz1,
El).

schimba([], _, _, []).
schimba([X|Rest], A, B, [B|Rest1]):- X == A, schimba(Rest,
A, B, Rest1).
schimba([X|Rest], A, B, [A|Rest1]):- X == B, schimba(Rest,
A, B, Rest1).
schimba([X|Rest], A, B, [X|Rest1]):- X \= A, X \= B,
schimba(Rest, A, B, Rest1).
Implementarea in Lisp
• Inregistrarea conexiunilor dintre orase si costurilor asociate.

(setf (get 'n1 'conectat) '(n2 n3 n4 n5 n6 n7))


(setf (get 'n1 'costuri) '(10 25 30 15 30 40))
(setf (get 'n2 'conectat) '(n1 n3 n4 n5 n6 n7))
(setf (get 'n2 'costuri) '(10 20 10 25 35 45))
(setf (get 'n3 'conectat) '(n1 n2 n4 n5 n6 n7))
(setf (get 'n3 'costuri) '(25 20 5 20 25 7))
(setf (get 'n4 'conectat) '(n1 n2 n3 n5 n6 n7))
(setf (get 'n4 'costuri) '(30 10 5 30 15 20))
(setf (get 'n5 'conectat) '(n1 n2 n3 n4 n6 n7))
(setf (get 'n5 'costuri) '(15 25 20 30 17 10))
(setf (get 'n6 'conectat) '(n1 n2 n3 n4 n5 n7))
(setf (get 'n6 'costuri) '(30 35 25 15 17 10))
(setf (get 'n7 'conectat) '(n1 n2 n3 n4 n5 n6))
(setf (get 'n7 'costuri) '(40 45 7 20 10 10))
Functia principala si ciclul de hill-climbing
• Se incepe cu o configuratie initiala si iteratia 0.
• Pana cand se ajunge la numarul de iteratii maxim,
se incearca “urcarea” intr-o configuratie mai buna.
• Atunci cand algoritumul se opreste, se intoarce
drumul optim si costul sau.

(defun porneste ()
(hill_climber '(n1 n2 n3 n4 n5 n6 n7) 0))

(defun hill_climber (l it)


(if (= it 200) (cons (performanta l) l)
(hill_climber (climb l) (+ it 1))))
“Urcarea” intr-o configuratie mai buna
• Se genereaza o noua configuratie a drumului prin
schimbul valorilor intre doua pozitii aleator
selectate.
• Daca evaluarea acesteia este mai buna decat cea a
nodului curent, se retine noul drum drept cel
curent.

(defun climb (l)


(if (< (performanta (mutatie l)) (performanta l)) (get
'lista 'mut) l))

(defun mutatie (l)


(setf (get 'lista ' mut) (schimba l (cauta l (+ (random
7) 1)) (cauta l (+ (random 7) 1)))))
Evaluarea unei configuratii
• Drumul va fi permanent dat de lista nodurilor.
• Pentru a calcula costul sau, va trebui sa gasim lista
costurilor asociata acestei succesiuni de noduri, ce va fi
apoi insumata.

(defun performanta (l)


(evaluare (append (transforma l) (fl l))))

(defun evaluare (l)


(if (null l) 0
(+ (first l) (evaluare (rest l)))))

(defun fl (l)
(cons (gaseste (first (last l)) (first l)) '()))
Lista costurilor asociate drumului
• Pentru doua orase, se cauta pozitia celui de-al
doilea in lista de conexiuni a primului.
• Apoi se cauta costul corespunzator acestei pozitii
in lista costurilor asociat primului oras.
(defun transforma (l)
(if (null (rest l)) '() (cons (gaseste (first l) (second l))
(transforma (rest l)))))

(defun gaseste (x y)
(cauta (get x 'costuri) (cauta2 (get x 'conectat) y 1))
)
Doua functii de cautare
; intoarce elementul de pe pozitia poz din lista l
(defun cauta (l poz)
(if (= poz 1) (first l) (cauta (rest l) (- poz 1))
)
)

; intoarce pozitia elementului el din lista l


(defun cauta2 (l el p)
(if (eql el (first l)) p (cauta2 (rest l) el (+ p 1))
)
)
Mutatia asupra unei configuratii
• Se genereaza aleator doua pozitii in vector si se
interschimba elementele de la aceste locatii.
• Se retine noua configuratie, pentru cazul in care este mai
performanta.

(defun mutatie (l)


(setf (get 'lista ' mut) (schimba l (cauta l (+ (random 7) 1))
(cauta l (+ (random 7) 1)))))

(defun schimba (l p1 p2)


(cond ((null l) '())
((eql (first l) p1) (cons p2 (schimba (rest l) p1 p2)))
((eql (first l) p2) (cons p1 (schimba (rest l) p1 p2)))
(t (cons (first l) (schimba (rest l) p1 p2)))))
Implementarea in Java
d[0][1] = 10; d[2][3] = 5;
• Inregistrarea d[0][2] = 25; d[2][4] = 20;
conexiunilor intre d[0][3] = 30; d[2][5] = 25;
orase si a costurilor d[0][4] = 15; d[2][6] = 7;
asociate, plus d[0][5] = 30; d[3][4] = 30;
comutativitatea. d[0][6] = 40; d[3][5] = 15;
d[1][2] = 20; d[3][6] = 20;
d[1][3] = 10; d[4][5] = 17;
for (int i = 0; i < n; i++) d[1][4] = 25; d[4][6] = 10;
for (int j = 0; j < n; j++)
d[1][5] = 35; d[5][6] = 10;
d[j][i] = d[i][j];
d[1][6] = 45;
Functia principala
public void go()
{
int t = 1;

// initializarea drumului - primei solutii potentiale


for (int i = 0; i < n; i++)
drum[i] = i;
evalD = eval(drum);

while (t < it)


{
for (int i = 0; i < n; i++) // se lucreaza cu un drumTemp pentru modificari
drumTemp[i] = drum[i];
climb();
t++;
}

// afisam solutia finala si costul acesteia


for (int i = 0; i < n; i++)
System.out.print(drum[i] + " ");
System.out.println(" cu evaluarea " + evalD);
}
Urcarea intr-o noua solutie

public void climb()


{
mutatie();
double evalNou = eval(drumTemp);
if (evalNou < evalD)
{
for (int i = 0; i < n; i++)
drum[i] = drumTemp[i];
evalD = evalNou;
}
}
Evaluarea unei solutii

public double eval(int[] sol)


{
double evaluare = 0;

for (int i = 0; i < n - 1; i++)


evaluare += d[sol[i]][sol[i + 1]];
evaluare += d[sol[n - 1]][sol[0]];

return evaluare;
}
Mutatia asupra unei solutii
public void mutatie()
{
int p1 = -1, p2 = -1;

// se genereaza doua pozitii aleatoare in vectorul drumTemp,


// adica se aleg doua orase care se vor schimba in traseul posibil
while (p1 == p2)
{
p1 = (int)Math.round((n - 1)* Math.random());
p2 = (int)Math.round((n - 1)* Math.random());
}

// schimba pozitiile p1 si p2 in drumTemp


int temp;
temp = drumTemp[p1];
drumTemp[p1] = drumTemp[p2];
drumTemp[p2] = temp;
}
Ce sa alegem?
Programare procedurala Programare nonprocedurala
 Implementare Prolog
directa/clasica a  Inregistrare directa a
algoritmilor cunostintelor despre
 Eficienta problema
 Portabilitate  Recursivitate
 Implementare dificila a  Gandire speciala
cunostintelor despre Lisp
problema  Apropiere de gandirea
matematica
 Recursivitate
 Sintaxa dificila
Pana la examen…