Sunteți pe pagina 1din 22

1. Limbaje, gramatici şi expresii regulate.

Exerciții și aplicații

1. Obiective
Scopul acestei lucrări de laborator îl constituie deprinderea noţiunilor
fundamentale legate de limbajele formale şi a abilităţilor de lucru cu acestea în
contextul rezolvării de probleme practice. În urma parcurgerii acestei lucrări
studentul:
va şti ce este un alfabet, un cuvânt, un limbaj, o gramatică, şi care este
legătura dintre acestea;
va cunoaşte particularităţile limbajelor care aparţin clasei limbajelor
regulate;
va cunoaşte particularităţile gramaticilor regulate şi va putea descrie
diverse limbaje regulate folosind astfel de gramatici;
va şti ce este o expresie regulată şi va putea să descrie diverse limbaje
regulate folosind astfel de expresii;
va putea folosi utilitare de căutare după model de genul grep;
va şti să implementeze software căutări după model folosind expresii
regulate.

2. Breviar teoretic
Se prezintă în continuare principalele noţiuni teoretice necesare înţelegerii
prezentei lucrări de laborator. (A se vedea şi lucrările [1]-[5].)

2.1. Alfabet. Cuvânt. Limbaj

Un alfabet este o mulţime de simboluri. Acesta se notează de obicei cu .


Un cuvânt este o alăturare de mai multe simboluri în care contează ordinea.
Un cuvînt se defineşte pe o mulţime de simboluri, deci pe un alfabet.
Lungimea cuvântului reprezintă numărul de simboluri din acel cuvânt. Se
notează cu | |.
|0| = 1, |000| = 3, | | = 0.
0n reprezină simbolul 0 repetat de n ori. (Înseamnă că |0n| = n.)
Dacă u este un cuvânt, atunci u = u = u.
Un limbaj este o mulţime de cuvinte.
Dacă avem un alfabet , definim * ca fiind mulţimea tuturor cuvintelor
ce pot fi formate cu simboluri din . (Prin urmare, orice limbaj definit pe
7
alfabetul este o submulţime a mulţimii * .)
Exemplu: Fie ={0}. Atunci * ={ , 0, 00, 000, ...}, unde reprezintă
cuvântul vid (adică fără niciun simbol).
Definim = * \ { }.
*
mai poate fi scris şi astfel:
* 0 1 2 n
... ... .
n
unde reprezintă limbajul tuturor cuvintelor formate din n simboluri ale
alfabetului .

2.2. Tipuri de limbaje


Limbajele pot fi: finite (atunci când numărul de cuvinte din ele este finit) şi
infinite (atunci când numărul cuvintelor din ele este infinit).
O altă clasificare a limbajelor este în limbaje regulate şi în limbaje
independente de context.

Limbajele regulate pot fi reprezentate ca:


- mulţimi (reprezentare descriptivă)
*
Exemplu: L = {w | |w| = 3}.
- gramatici regulate (reprezentare generativă)
- expresii regulate. (Definirea limbajelor regulate este strâns legată de
modul de definire a acestor expresii.)
- automate finite.
Orice limbaj finit este un limbaj regulat. Dar există şi limbaje regulate care
sunt infinite. (Un exemplu tipic de astfel de limbaj regulat este limbajul
identificatorilor dintr-un limbaj de programare.)

Limbajele independente de context pot fi reprezentate ca:


- mulţimi
*
Exemplu: L = {w | w = 0n1n, n>0}.
- gramatici independente de context
- automate cu stivă
Un exemplu tipic de limbaj independent de context este limbajul
parantezelor echilibrate, adică limbajul L = {(), (()), (()()), (()(()())), ...}, definit pe
alfabetul = {(, )}. (În plus, instrucţiunile tipice întâlnite într-un limbaj de
programare sunt reprezentate formal ca limbaje independente de context.)

2.3. Gramatici
O gramatică ne arată nişte reguli cu ajutorul cărora putem să generăm
cuvinte.
8
Exemplu de gramatică: X | 0X, unde este cuvântul vid, 0 este un
simbol şi X este o variabilă. X este în acest caz şi variabilă de start (adică variabila
de la care se începe generarea). Citim regula de mai sus ca “X produce (sau
generează) sau 0X”. (Producţia (sau regula) aceasta este de fapt o formă
compactă de scriere a următoarelor două producţii: (1) X şi (2) X 0X.)
Un exemplu de utilizare a acestei gramatici pentru a genera cuvinte este reprezentat
în Figura 1.
O gramatică este, aşadar, un set de producţii (sau reguli de generare) care
descriu modalităţile posibile de a obţine cuvintele unui limbaj prin înlocuiri
succesive ale variabilelor pornind de la variabila de start. (Mai exact, se înlocuieşte
capul producţiei (adică ceea ce este în stânga semnului “ ”) cu corpul producţiei
(adică ceea ce este în dreapta semnului “ ”).)

Figura 1. Exemplu de generare a cuvintelor , 0, 00 şi 000 pentru gramatica


X | 0X.

Un alt exemplu de gramatică este arătat în continuare. Este vorba de o


gramatică ce generează identificatorii valizi din limbajul C (excluzând, pentru a
evita confuziile, literele mari). Variabila de start este variabila I, iar celelalte
variabile sunt X, Y şi Z.
I XY
X _ | a | b | ... | z
Y | ZY
Z _ | a | b | ... | z | 0 | 1 | 2 | ... | 9

Această gramatică a fost obţinută pornind de la descrierea prin limbaj


natural a identificatorilor valizi din C, care spune că (1) trebuie să înceapă cu o
literă sau cu simbolul “underline” şi (2) poate să continue cu un număr oarecare de
litere, cifre sau “_”. Partea (1) a definiţiei a fost tradusă prin variabila X, iar partea
(2) prin variabila Y. (Se observă utilizarea recursivă a variabilei Y împreună cu
alternativa de cuvânt vid ( ) pentru a descrie un număr variabil de simboluri.)

9
În particular, o gramatică regulată este o gramatică ce este fie de tip
liniară la dreapta, fie de tip liniară la stînga.
În partea dreaptă a producţiilor unei gramatici liniare la dreapta poate să
apară maxim o variabilă şi dacă apare să fie situată pe ultima poziţie.
Exemplu de gramatică regulată liniară la dreapta:
X aY
Y | aY
În partea dreaptă a producţiilor unei gramatici liniare la stânga poate să
apară maxim o variabilă şi dacă apare să fie situată pe prima poziţie.
Exemplu de gramatică regulată liniară la stânga:
X Ya
Y |Ya

2.4. Expresii regulate


O expresie regulată este o formă de reprezentare sub formă de “model” a
limbajelor regulate folosindu-se doar simbolurile alfabetului pe care e definit
limbajul, plus operatorii ŞI, SAU şi “stea”.
Orice simbol sau alăturare de două sau mai multe simboluri este o
expresie regulată.
Exemplu de expresii regulate (considerând alfabetul format din toate
literele mici): a, , ef, aa.
Dacă e şi f sunt două expresii regulate, atunci ef este o expresie
regulată ce semnifică faptul că se aşteaptă să apară expresia e urmată
de expresia f. (Operatorul ŞI.)
Dacă e şi f sunt două expresii regulate, atunci e+f este o expresie
regulată ce semnifică faptul că poate să apară fie expresia e, fie
expresia f. (Operatorul SAU.)
Dacă e este o expresie regulată, atunci e* este o expresie regulată ce
semnifică faptul că expresia e poate să apară fie niciodată, fie o singură
dată, fie de două ori, ..., fie de n ori. Practic expresia regulată e* este
echivalentă cu expresia regulată +e+(ee)+(eee)+... .
Dacă e este o expresie regulată, atunci prin L(e) notăm limbajul format
din cuvintele descrise de acea expresie.
Exemple:
1) L( (01)* ) = { , 01, 0101, 010101, ...}
2) L( 0(1+2)* ) = {0, 01, 02, 011, 012, 021, 022, 0111, ...}

2.5. Grep
Utilitarul grep permite căutarea de text după model, specificarea modelului
căutat făcându-se cu ajutorul unei expresii regulate. Se găseşte în sistemul de
10
operare Linux, dar există şi versiuni pentru Windows, cum este WinGrep.

Expresiile regulate recunoscute de grep (mai exact, de varianta egrep) şi de


WinGrep folosesc următorii operatori:
[] – semnifică faptul că se aşteaptă unul dintre simbolurile puse între
parantezele drepte.
Exemplu: [abcb] – este echivalentă cu expresia regulată (a+b+c+d); se
putea scrie mai compact ca [a-d].
. – reprezintă orice caracter.
? – semnifică faptul că expresia din stânga lui poate să apară maxim o dată.
Exemplu: a? – este echivalentă cu expresia regulată ( +a).
* – are acceaşi semnificaţie ca operatorul „star” de la expresii regulate.
+ – semnifică faptul că expresia din stânga lui poate să apară cel puţin o
dată.
Exemplu: a+ – este echivalentă cu expresia regulată aa*.
| – are aceeaşi semnificaţie ca operatorul “+” de la expresii regulate.
() – grupează o subexpresie (pentru a putea modifica regulile de precedenţă
a operatorilor).
^ – semnifică început de linie de text.
$ – semnifică sfârşit de linie de text.
Dacă se doreşte căutarea unuia dintre aceste caractere speciale, el trebuie precedat
de caracterul “\”.

11
3. Exemple şi probleme rezolvate
Se vor studia şi reface următoarele probleme rezolvate, în scopul unei bune
înţelegeri a lor.

Problema 1: Se dă alfabetul = {a, b, c}. Să se scrie o expresie regulată pentru


cuvintele care conţin cel puţin un a şi un b.

Soluție:
Cuvintele ce conţin cel puţin un a şi un b sunt de forma
... a ... b ...
sau
... b ... a ...
unde “...” înseamnă orice cuvânt format cu simboluri din mulţimea {a, b, c}.
Conform afirmaţiei de mai sus, expresia cerută este
(a+b+c)*a(a+b+c)*b(a+b+c)* +
(a+b+c)*b(a+b+c)*a(a+b+c)*
unde (a+b+c)* înseamnă că fie simbolul a, fie simbolul b, fie simbolul c poate
apărea de 0 ori (niciodată), o dată, de două ori, etc. (la fiecare pas putând apărea
oricare dintre ele).

Figura 2. Rezultatul utilizării în grep a expresiei regulate de la Problema 1.


12
Pentru a testa expresia construită în grep (sub Linux) vom parcurge următorii paşi:
1. Deschidem un terminal (Konsole).
2. Creăm un fişier text folosind un editor de text disponibil (de exemplu,
gedit):
gedit ex1.txt
Această comandă va deschide fişierul text cu numele “ex1.txt” în care putem scrie
cuvinte formate cu simboluri din alfabetul {a, b, c}. De exemplu:
ccc abacab bcbcb cab bac babac abba aba baba
3. Testarea efectivă în grep se va face astfel:
egrep '([a-c]*a[a-c]*b[a-c]*)|([a-c]*b[a-c]*a[a-c]*) ex1.txt'
unde
[a-c]* este expresia grep pentru (a+b+c)*
ex1.txt este fişierul text în care am scris la pasul 2 cuvinte de căutat.
Rezultatul poate fi văzut în Figura 2.

Problema 2: Se dă alfabetul = {0, 1}. Să se scrie o expresie regulată pentru


*
limbajul L = {w | antepenultimul simbol din w este 1}.

Soluție:
Cuvintele cerute la această problemă sunt de forma “0 sau 1, repetat de un număr
nespecificat de ori, urmat de 1, urmat de 0 sau 1, urmat de 0 sau 1”. Înseamnă că
expresia căutată este
(0+1)*1(0+1)(0+1)
Testarea cu grep se poate face astfel
egrep '[01]*1[01][01] ex2.txt'
Rezultatul testării aceste expresii este ilustrat in Figura 3.

Figura 3. Rezultatul testării în grep a expresiei de la Problema 2.

Problema 3: Se dă alfabetul = {0, 1}. Să se scrie o expresie regulată pentru


*
limbajul L = {w | w conţine a singură pereche de 11}.
13
Soluție:
Cuvântul căutat este de forma
... 11 ...
unde
primele “...” înseamnă “orice cuvânt format din simbolurile 0 şi 1 care nu
conţine 11 şi nu se termină cu 1”.
Prima parte a expresiei cerute este, deci
((1+ )(00*(1+ ))*0)*
Această expresie reprezintă cuvinte ce încep cu 1 sau cu 0, conţin 1 doar
izolat (dacă îl conţin) şi se termină cu 0.
ultimele “...” înseamnă “orice cuvânt format din simbolurile 0 şi 1 care nu
conţine 11 şi nu începe cu 1”.
Ultima parte a expresiei cerute este, deci
(0(1+ )(00*(1+ ))*)*
Această expresie reprezintă cuvinte ce încep cu 0 şi conţin 1 doar izolat
(dacă îl conţin).
Aşadar, întreaga expresie cerută este
((1+ )(00*(1+ ))*0)*11(0(1+ )(00*(1+ ))*)*
Testarea expresiei în grep se poate face folosind comanda
egrep '(1?(00*1?)*0)*11(0(1?00*)*1?)*' ex3.txt
Rezultatul acestei operaţii este ilustrat în Figura 4.

Figura 4. Rezultatul testării în grep a expresiei de la Problema 3.

Problema 4: Se dă alfabetul = {0, 1}. Să se scrie o expresie regulată pentru


limbajul următor:
*
L = {w | numărul de zerouri din w este divizibil cu 3}.

Soluție:
Cerinţa problemei ne indică faptul că cuvântul căutat conţine ori 3 zerouri, ori 6
zerouri, ori..., ori 3k zerouri, adică un număr de zerouri care e multiplu de 3.Între
aceste zerouri se pot afla oricâţi de 1.
Înseamnă că expresia căutată este următoarea:

14
(1*01*01*01*)*
Expresia de test în grep se scrie în felul următor:
egrep '(1*01*01*01*)+' ex4.txt
Rezultatul căutării este prezentat în Figura 5.

Figura 5. Rezultatul testării în grep a expresiei de la Problema 4.

Problema 5: Se dă alfabetul = {0, 1}. Să se scrie o expresie regulată pentru


limbajul următor:
*
L = {w | w exprimă în binar un număr mai mare decât 40 şi mai mic decât 48}

Soluție:
Scriem numerele 40 si 48 în binar:
40(10) = 101000(2)
48(10) = 110000(2)
Înseamnă că limbajul L este format din următoarele cuvinte:
L = {101001, 101010, 101011, 101100, 101101, 101110, 101111}.

Cum limbajul L este finit, cea mai simplă metodă de a scrie o expresie regulată
pentru a descrie cuvintele din el este să punem “+” între toate cuvintele sale. La o
privire mai atentă asupra cuvintelor limbajului observăm că acestea nu sunt
complet independente, ci respectă un model:
Primele 3 simboluri sunt 101.
Ultimele 3 simboluri sunt orice combinaţie de 0 şi 1.
Rezultă că expresia regulată pentru limbajul L este:
101(0+1)(0+1)(0+1)
Transcrierea acestei expresii în formatul recunoscut de grep se poate face în felul
următor:
egrep ‘101[01][01][01]’ ex5.txt
Testarea ei rămâne ca exerciţiu.

Problema 6: Să se scrie o expresie regulată pentru limbajul identificatorilor valizi


din limbajul C.

15
Soluție:
Identificatorii din limbajul C (sau Java, sau C#) respectă următoarele reguli:
încep cu literă (mică sau mare) sau cu simbolul “_” (underline)
şi pot avea apoi litere, cifre sau “_”.
Expresia regulată pentru limbajul acestor identificatori este, aşadar
(a+b+...+z+A+...+Z+_)+(a+b+...+z+A+...+Z+0+1+...+9+_)*
Această expresie poate fi transcrisă în formatul recunoscut de grep astfel
egrep ‘[a-zA-Z_][a-zA-Z0-9_]*’ ex6.txt

Problema 7: Să se implementeze un program care recunoaşte limbajul de la


Problema 2.

Soluția 1:
Implementăm un program în C# care realizează următoarele:
testează dacă string-ul introdus este format numai din 0 şi 1 prin utilizarea
metodei IsMatch din clasa RegEx care primeşte ca parametri string-ul şi
pattern-ul “^[0-1]+$”
şi dacă string-ul corespunde condiţiei, testează dacă antepenultimul simbol
este 1.

Codul sursă al programului este următorul:

16
Soluția 2:
Implementăm un program similar în Java. Codul sursă este următorul:

17
18
În Figura 6 sunt arătate câteva rezultate ale rulării programului.

Figura 6. Rezultate ale rulării programului prezentat în Soluţia 2 de la Problema 7.

Problema 8: Să se implementeze un program care recunoaşte expresia regulată de


la Problema 6.

Soluție:
Realizăm programul în Gtk# pe platforma Mono din Linux. Interfaţa grafică va
conţine o arie de text în care putem scrie string-ul şi un buton la apăsarea căruia
19
suntem anunţaţi dacă string-ul respectiv reprezintă sau nu un identificator valid în
limbajul C.
Validitatea identificatorilor se testează folosind metoda statică IsMatch din clasa
RegEx, iar expresia de test este similară celei de la Problema 6:
^[a-zA-Z_][a-zA-Z0-9_]*

Codul sursă al programului este prezentat în continuare:

20
21
Exemple de rulare a programului sunt arătate în Figura 7.

Figura 7. Exemple de rulare a programului de la Problema 8.

Problema 9: Să se implementeze un program care recunoaşte dacă un cuvânt


introdus de la tastatură corespunde unei expresii regulate (tot introdusă de la
tastatură şi care conţine doar simbolurile 0, 1 şi operatorii + şi *).

Soluție:
Realizăm programul în C# sub Linux folosind Gtk#. Validarea cuvântului şi a
expresiei introduse de la tastatură impune ca acestea să nu conţină decât
simbolurile 0, 1, + şi * şi se realizează cu metoda IsMatch din clasa RegEx ce
primeşte ca parametri string-ul de verificat şi pattern-ul “^[0-1+*]+$”. Compararea
dintre cuvântul şi expresia regulată introduse de la tastatură se face apoi prin
aceeaşi metodă.

Codul sursă al programului este arătat mai jos:

22
23
24
25
26
Rezultate ale rulării programului sunt arătate în Figura 8.

Figura 8. Exemple de rulare a programului de la Problema 9.

27
4. Desfăşurare
1. Să se parcurgă pas cu pas toate problemele rezolvate prezentate
anterior. (Pentru sistemele care nu au Linux se va folosi în loc de grep
aplicatia wingrep. Pentru problemele care necesită programare, se pot
folosi pentru sistemele cu Windows aplicaţiile Code::Blocks (pentru
C/C++) sau NetBeans (pentru Java).)
2. Pentru problemele de la 1 la 5 să se construiasca pentru respectivul
limbaj o gramatică regulată: (1) linară la stânga; (2) liniară la dreapta.
3. Pentru problema 6 o gramaticî independentă de context a fost dată în
breviarul teoretic. Să se construiască o gramatică regulată.
4. Să se scrie un program care rezolvă problema 7 fără să folosească
funcţii de bibliotecă pentru recunoaşterea expresiilor regulate.
(Limbajul de programare utilizat este la alegere.)
5. Să se scrie un program care rezolvă problema 8 fără să folosească
funcţii de bibliotecă pentru recunoaşterea expresiilor regulate.
(Limbajul de programare utilizat este la alegere.)
6. Să se scrie un program care rezolvă problema 9 fără să folosească
funcţii de bibliotecă pentru recunoaşterea expresiilor regulate.
(Limbajul de programare utilizat este la alegere.)
7. Cât de mult s-ar complica exerciţiul anterior dacă s-ar cere
recunoaşterea oricărei expresii regulate (folosind simbolurile 0 şi 1,
operatorii + şi *, precum şi parantezele rotunde)?

28

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