This shows you the differences between two versions of the page.
| — |
pp:26:laboratoare:tda [2026/02/23 10:04] (current) mihaela.balint created |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Tipuri de Date Abstracte ====== | ||
| + | |||
| + | * Data publicării: 19.02.2026 | ||
| + | * Data ultimei modificări: 19.02.2026 | ||
| + | |||
| + | ===== Obiective ===== | ||
| + | Acest laborator introduce conceptul de **Tip de Date Abstract (TDA)**, un instrument fundamental pentru gestionarea complexității în programare și un pas important către paradigma funcțională. | ||
| + | |||
| + | Aspecte urmărite: | ||
| + | * **Utilitatea TDA-urilor**: Înțelegerea rolului lor în abstractizarea datelor. | ||
| + | * **Specificație**: Definirea unui tip prin constructori, operatori și axiome. | ||
| + | * **Demonstrații prin inducție structurală**: Validarea matematică a proprietăților codului. | ||
| + | * **Axiomele ca bază pentru programarea funcțională**: Tranziția de la logică la cod valid. | ||
| + | |||
| + | ===== Tipuri de Date Abstracte ===== | ||
| + | Un **TDA (Tip de Date Abstract)** este un model matematic pentru un anumit tip de date, definit nu prin modul în care este stocat în memorie, ci prin **comportamentul** său (ce valori aparțin tipului, ce operații se pot face asupra lor, și ce reguli respectă aceste operații). | ||
| + | |||
| + | **De ce este important pentru Programarea Funcțională?** | ||
| + | Ca și în matematică, în programarea funcțională nu ne concentrăm pe starea memoriei sau pe pașii de execuție, ci pe transformarea datelor de intrare în rezultate. TDA-urile ne forțează să gândim în termeni de **structură** și **recurență**, pregătindu-ne pentru scrierea de cod declarativ, sigur și ușor de testat. | ||
| + | |||
| + | Un TDA este definit prin trei elemente: | ||
| + | * **Constructori de bază**: Funcții care creează valorile tipului (ex: cum "construim" un număr sau o listă). | ||
| + | * **Operatori**: Funcții care procesează sau interoghează datele (ex: adunare, lungimea listei). | ||
| + | * **Axiome**: Reguli matematice care descriu efectul operatorilor asupra tuturor valorilor tipului. | ||
| + | |||
| + | ==== Exemple (deocamdată fără axiome) ==== | ||
| + | ^ Tip ^ Constructori de bază ^ Operatori ^ | ||
| + | | **Nat (N)** | zero: -> N \\ succ: N -> N | add: N x N -> N \\ isZero: N -> Bool | | ||
| + | | **List (L)** | null: -> L \\ cons: E x L -> L | head: L -> E \\ tail: L -> L \\ len: L -> N \\ app: L x L -> L | | ||
| + | ==== Constructori de tip ==== | ||
| + | Toți operatorii unui tip $t$ care produc o valoare de tip $t$ se numesc **constructori**. | ||
| + | * **Exemplu (Nat)**: ''zero'', ''succ'' și ''add'' sunt constructori deoarece toți returnează un număr natural. | ||
| + | * **Contraexemplu**: Operatorul ''isZero'' (care verifică dacă un număr este zero) **nu** este un constructor pentru ''Nat'', deoarece produce un ''Bool''. | ||
| + | |||
| + | ==== Constructori de bază ==== | ||
| + | Constructorii de bază reprezintă un set de constructori **necesari și suficienți** pentru a genera toate valorile posibile ale tipului. | ||
| + | * **Exemplu (Nat)**: ''zero'' și ''succ'' sunt constructori de bază. | ||
| + | * **Notă**: ''add'' nu este constructor de bază, deoarece orice număr natural poate fi obținut folosind doar ''zero'' și ''succ''. | ||
| + | |||
| + | ==== Clasificarea constructorilor de bază (după parametri) ==== | ||
| + | Constructorii unui TDA $t$ se împart în: | ||
| + | * **Nulari**: Au 0 argumente. Sunt punctele de plecare (ex: ''zero'' pentru ''Nat'', ''null'' pentru ''List''). | ||
| + | * **Externi**: Au argumente, dar niciunul nu este de tipul $t$. (În fișa tipurilor ''Nat'' și ''List'' nu există constructori externi). | ||
| + | * **Interni**: Au cel puțin un argument de tip $t$. Aceștia permit recursivitatea structurii (ex: ''succ'' pentru ''Nat'', ''cons'' pentru ''List''). | ||
| + | |||
| + | ==== Axiome ==== | ||
| + | Axiomele specifică modul în care se comportă operatorii TDA-ului pe toate valorile acestuia. | ||
| + | |||
| + | **Reguli pentru scrierea axiomelor:** | ||
| + | * **Acoperirea**: Axiomele trebuie să acopere toate valorile tipului. De aceea, definim comportamentul operatorilor în raport cu **constructorii de bază**. | ||
| + | * **Neredundanța**: Pentru facilitarea demonstrațiilor, scriem axiome minimale. | ||
| + | * **Exemplu**: La operația ''add(n, m)'', variem doar primul parametru între ''zero'' și ''succ(n)''. Nu este necesar să variem și al doilea parametru. În general, când există mai mulți parametri de același tip, scriem axiomele variind doar parametrii pe care vom face recursivitatea. | ||
| + | |||
| + | ==== Exemple (specificații complete) ==== | ||
| + | ^ Tip ^ Constructori de bază ^ Operatori ^ Axiome ^ | ||
| + | | **Nat (N)** | zero: -> N \\ succ: N -> N | add: N x N -> N \\ isZero: N -> Bool | (A1) add(zero, m) = m \\ (A2) add(succ(n), m) = succ(add(n, m)) \\ \\ (Z1) isZero(zero) = true \\ (Z2) isZero(succ(n)) = false | | ||
| + | | **List (L)** | null: -> L \\ cons: E x L -> L | head: L -> E \\ tail: L -> L \\ len: L -> N \\ app: L x L -> L | (H1) head(null) = eroare \\ (H2) head(cons(x, L)) = x \\ \\ (T1) tail(null) = eroare \\ (T2) tail(cons(x, L)) = L \\ \\ (L1) len(null) = zero \\ (L2) len(cons(x, L)) = succ(len(L)) \\ \\ (P1) app(null, L2) = L2 \\ (P2) app(cons(x, L1), L2) = cons(x, app(L1, L2)) | | ||
| + | |||
| + | ===== Dimensiunea unei valori și Inducția Structurală ===== | ||
| + | |||
| + | ==== Dimensiunea unei valori ==== | ||
| + | Dimensiunea unei valori $v$ a unui TDA reprezintă **numărul de constructori de bază** din formula care construiește valoarea. | ||
| + | * **Exemplu**: Valoarea ''succ(succ(zero))'' are dimensiunea **3** (trei constructori de bază). | ||
| + | |||
| + | ==== Inducția Structurală ==== | ||
| + | Inducția structurală este un caz particular al inducției bine formate, și se folosește pentru a demonstra că o proprietate $P(v)$ este adevărată pentru orice valoare $v$ a unui TDA: | ||
| + | * **Cazul de bază**: \\ Demonstrăm $P(v)$ pentru valori de dimensiune minimă (constructori nulari sau externi). | ||
| + | * **Pasul inductiv**: \\ Presupunem că $P$ este adevărată pentru toate valorile de dimensiune $< n$ (ipoteza inductivă) și demonstrăm pentru valori de dimensiune $n$ (obținute prin constructori interni). | ||
| + | | ||
| + | ==== Exemplu pe tipul Nat ==== | ||
| + | Demonstrăm că ''add(n, zero) = n'', pentru orice număr natural ''n''. | ||
| + | * **Cazul de bază**: constructorul nular ''zero'' \\ add(zero, zero) = zero (conform axiomei ''A1''). | ||
| + | * **Pasul inductiv**: constructorul intern ''succ'' \\ Ipoteza inductivă: add(n, zero) = n. Demonstrăm proprietatea pentru ''succ(n)'': | ||
| + | * add(succ(n), zero) = succ(n) <=> (conform ''A2'') \\ succ(add(n, zero)) = succ(n) <=> (conform ''ipotezei inductive'') \\ succ(n) = succ(n) **q.e.d** | ||
| + | |||
| + | |||
| + | |||
| + | ===== Gândirea funcțională: axiomele sunt cod ===== | ||
| + | În programarea funcțională, scrierea codului este practic scrierea axiomelor. Nu mai gândim în termeni de "ce pași fac", ci "care este definiția cazului". | ||
| + | |||
| + | Chiar dacă problemele nu sunt formulate ca operatori ai unui TDA, procesul de "gândire funcțională" este următorul: | ||
| + | * **Pe ce variabile este util să fac recursivitate?** \\ Identificăm variabilele care se "micșorează" structural. | ||
| + | * **Cum mă ajută rezultatul subproblemei?** \\ Ex: Dacă știu rezultatul pentru ''tail(L)'', cum obțin rezultatul pentru ''L''? | ||
| + | |||
| + | ==== Exemple de gândire ==== | ||
| + | |||
| + | <code haskell> | ||
| + | nth (al n-lea element din listă) | ||
| + | </code> | ||
| + | |||
| + | **Întrebare**: Pe ce variabile fac recursivitate? \\ | ||
| + | **Raționament**: A identifica al ''n''-lea element din ''L'' înseamnă a identifica al ''(n-1)''-lea element din ''tail(L)''. \\ | ||
| + | **Concluzie**: Pe numărul ''n'' și pe lista ''L''. | ||
| + | |||
| + | <code haskell> | ||
| + | -- Axiome (cod) | ||
| + | nth(null, n) = eroare | ||
| + | nth(cons(x, L), zero) = x | ||
| + | nth(cons(x, L), succ(n)) = nth(L, n) | ||
| + | </code> | ||
| + | |||
| + | |||
| + | <code haskell> | ||
| + | duplicate (duplicarea fiecărui element al listei) | ||
| + | </code> | ||
| + | |||
| + | **Notă**: Există un singur parametru (''L''), deci o singură variabilă pe care pot face recursivitate. \\ | ||
| + | **Întrebare**: Cum transform rezultatul pentru ''tail(L)''? \\ | ||
| + | **Raționament**: Dacă am duplicat toate elementele din ''tail(L)'', trebuie doar să adaug de două ori ''head(L)'' la început. | ||
| + | |||
| + | <code haskell> | ||
| + | -- Axiome (cod) | ||
| + | duplicate(null) = null | ||
| + | duplicate(cons(x, L)) = cons(x, cons(x, duplicate(L))) | ||
| + | </code> | ||
| + | |||
| + | ===== Utilitatea TDA-urilor ===== | ||
| + | Abstractizarea datelor folosind TDA-uri are consecințe importante: | ||
| + | * **Încapsularea detaliilor de implementare**: Utilizatorul unui TDA știe $ce$ face o operație (conform axiomelor), nu $cum$ este ea implementată. Putem schimba reprezentarea internă (ex: o listă stocată ca vector sau ca listă înlănțuită) fără a afecta codul care utilizează TDA-ul. | ||
| + | * **Integritatea datelor**: Deoarece datele sunt accesibile doar prin constructori și operatori, se previne modificarea necorespunzătoare a reprezentării interne. | ||
| + | * **Modularitate și reutilizare**: Fiecare TDA este o componentă independentă, utilizabilă prin intermediul interfeței sale, asemănător cu tipurile predefinite (numere, liste, șiruri de caractere). | ||
| + | * **Validare formală și automatizată**: Axiomele sunt atât cod cât și reguli de rescriere, permițând verificarea prin inducție a faptului că programul respectă specificațiile în orice condiții. Pentru că raționamentul se face direct asupra codului, procesul de validare poate fi automatizat. | ||
| + | |||
| + | ===== Exerciții ===== | ||
| + | * Demonstrați prin inducție structurală proprietatea ''len(app(A, B)) = add(len(A), len(B))''. | ||
| + | * Definiți TDA-ul ''Arbore binar cu elemente de tip E'', cu operatorii ''size'' (numărul de elemente din arbore) și ''flatten'' (lista rezultată din parcurgerea arborelui în inordine). | ||
| + | * Pentru tipul anterior, demonstrați prin inducție structurală proprietatea ''size(T) = len(flatten(T))''. Când demonstrația se blochează, avansați demonstrând o proprietate adițională cât mai simplă. | ||
| + | * Scrieți axiome (cod) pentru următoarele operații: | ||
| + | * **Rotația** unei liste cu ''n'' poziții către stânga (ex: $[a, b, c, d, e, f, g]$ rotit cu două poziții devine $[c, d, e, f, g, a, b]$). | ||
| + | * **Ștergerea** elementului de la poziția ''n'' din listă (ex: ștergerea celui de-al treilea element din $[a, b, c, d, e, f, g]$ produce $[a, b, d, e, f, g]$). | ||
| + | * **Simetria structurală**: Verificați dacă doi arbori binari sunt simetrici (ex: un arbore cu structura ''node(empty, x, node(empty, y, empty))'' este simetric cu unul de forma ''node(node(empty, z, empty), w, empty)''). | ||
| + | |||
| + | |||
| + | ===== Referințe ===== | ||
| + | * [[https://www.ic.unicamp.br/~meidanis/courses/mc336/problemas-lisp/L-99_Ninety-Nine_Lisp_Problems.html|Probleme de programare funcțională]] | ||