Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Tema 2 PP 2024 ====== <note important> **DEADLINE TBD** * Temele trebuie submise pe curs.upb.ro, in assignment-ul numit ''Tema 2 Scala''. * Temele ce nu sunt acceptate de validatorul de arhive **NU** vor fi puncate. * Vom folosi vesiunea Scala **TBD** si **TBD**. </note> <note warning> **Folosiți un stil de programare funcțional. NU se vor accepta:** * **Efecte laterale** (de exemplu modificarea parametrilor dați ca input la funcție) * **var** (**val** este ok!) </note> <note>Scheletul se poate găsi la: TBD </note> ==== Scopul Temei ==== In cadrul acestei teme veti implementa un Query Language inspirat de SQL, ce utilizeaza o baza de date implementata de voi. Va vom ghida in realizarea operatiunilor atat pe tabele individuale, cat si pe interactiuni intre mai multe tabele, pe care apoi le veti putea combina. ==== Reprezentarea Tabelelor ==== Considerati exemplul de mai jos. ^ Nume ^ Prenume ^ Varsta ^ | Popescu | Ion | 30 | | Ionescu | Maria | 25 | Acest tabel poate fi reprezentat ca: <code scala> type Row = Map[String, String] // nume_coloana - valoare type Tabular = List[Row] </code> <hidden Funcții utile pentru lucrul cu Map> <code scala> let map = Map(1 -> 2, 3 -> 4): Map[Int, Int] </code> * Adauga o noua pereche cheie-valoare <code scala> map + (5 -> 6) // Map(1 -> 2, 3 -> 4, 5 -> 6) map + (3 -> 5) // Map(1 -> 2, 3 -> 5) -- if key exists, it updates the value </code> * Eliminarea unei perechi cheie-valoare <code scala> map - (3 -> 4) // Map(1 -> 2) </code> * Accesarea valorii asociate unei chei <code scala> map get 1 // return 2 map get 3 // return 4 map getOrElse (1, 0) // return 2 map getOrElse (5, 0) // return 0 - daca cheia nu exista, returneaza valoarea default map contains 1 // True map contains 5 // False </code> * Functii de orin superior <code scala> map mapValues (x => x + 5) // Map(1 -> 7, 2 -> 9) map filterKeys (x => x <= 2) // Map(1 -> 2) </code> * Combinarea a doua map-uri <code scala> val map1: Map[Int, Int] = Map(1 -> 2, 3 -> 4) val map2: Map[Int, Int] = Map(5 -> 6, 7 -> 8) map ++ map2 // Map(1 -> 2, 3 -> 4, 5 -> 6, 7 -> 8) </code> </hidden> ==== Clasa Table ==== Vom defini un tabel ca o clasa care are ca atribute numele tabelei ''tableName'' si datele ''tableData''. <code scala> case class Table (tableName: String, tableData: Tabular) { def header: List[String] = ??? def data: Tabular = ??? def name: String = ??? } </code> **1.1.** Definiti metoda ''toString'' care returneaza tabelul in forma CSV. <code scala> override def toString: String = ??? </code> **1.2** Definiti operatia de inserare a unei linii in tabel. <code scala> def insert(row: Row): Table = ??? </code> **1.3** Definiti operatia de stergere a tuturor liniilor exact egale cu cea primita ca parametru. <code scala> def delete(row: Row): Table = ??? </code> **1.4.** Definiti operatia de sortare a liniilor din tabel dupa o anumita coloana. <code scala> def sort(column: String): Table = ??? </code> **1.5.** Definiti functia select care primeste o lista de stringuri si returneaza un nou obiect de tip Table ce contine doar coloanele specificate. <code scala> def select(columns: List[String]): Table = ??? </code> **1.6.** Definiti functia apply intr-un **companion object** al clasei ''Table''. Functia trebuie sa parseze un sir de caractere si sa returneze un tabel cu numele dat. <code scala> def apply(name: String, s: String): Table = ??? </code> ==== Filtre peste Tabele ==== <code> <filter> ::= <filter> && <filter> | <filter> || <filter> | <filter> == <filter> | !<filter> | any [ <filter> ] | all [ <filter> ] | operation [ <filter> ] </code> **2.1.** Vom defini operatia de filtrare a datelor dintr-o tabela sub forma unui TDA. Acest lucru ne permite sa definim operatii de filtrare complexe, compuse din mai multe conditii. Acest TDA are urmtorii constructori: - Field - reprezinta o conditie de filtrare pe un camp al tabelei. Aceasta conditie este satisfacuta daca valoarea de pe coloana specificata respecta predicatul. - Compound - reprezinta o conditie de filtrare compusa din mai multe conditii. Aceasta conditie este satisfacuta daca toate conditiile din lista conditions sunt satisfacute. - Not - reprezinta negarea unei conditii de filtrare. - And - reprezinta conjunctia a doua conditii de filtrare. - Or - reprezinta disjunctia a doua conditii de filtrare. - Equal - reprezinta o conditie de egalitate intre doua conditii de filtrare. - Any - reprezinta o conditie de filtrare care este satisfacuta daca cel putin una dintre conditiile din lista este satisfacuta. - All - reprezinta o conditie de filtrare care este satisfacuta daca toate conditiile din lista sunt satisfacute. <code scala> trait FilterCond { def eval(r: Row): Option[Boolean] } case class Field(colName: String, predicate: String => Boolean) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } case class Compound(op: (Boolean, Boolean) => Boolean, conditions: List[FilterCond]) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } case class Not(f: FilterCond) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } def And(f1: FilterCond, f2: FilterCond): FilterCond = ??? def Or(f1: FilterCond, f2: FilterCond): FilterCond = ??? def Equal(f1: FilterCond, f2: FilterCond): FilterCond = ??? case class Any(fs: List[FilterCond]) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } case class All(fs: List[FilterCond]) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } </code> **2.2.** Pentru a simplifica definirea conditiilor de filtrare, vom defini cateva operatori care sa ne permita sa scriem cod mai concis. Vom folosi urmatorii operatori ce extind clasa FilterCond: - === - pentru a verifica egalitatea a doua conditii de filtrare. - && - pentru a face conjunctia a doua conditii de filtrare. - || - pentru a face disjunctia a doua conditii de filtrare. - !! - pentru a nega o conditie de filtrare. <code scala> // se scoate?? extension (f: FilterCond) { def ==(other: FilterCond) = ??? def &&(other: FilterCond) = ??? def ||(other: FilterCond) = ??? def !! = ?? // Puteti sa adaugati mai multi operatori :) } </code> **2.3.** Definiti operatia de filtrare a liniilor din tabel care respecta o anumita conditie. <code scala> def filter(f: FilterCond): Table = ??? </code> **2.4.** Definiti operatia de update a unei linii din tabel. Funcția primeste ca input o conditie care dicteaza liniile ce vor fi modificate. Valorile schimbate se găsesc intr-un Map[nume_coloana, valoare_noua]. Trebuiesc modificate TOATE liniile ce respectă condiția. <code scala> def update(f: FilterCond, updates: Map[String, String]): Table = ??? </code> ==== Operatii cu una sau mai multe Tabele ===== O baza de date contine mai multe tabele, pe care putem aplica o serie de operatii: - create - creaza o noua tabela cu nume unic si o lista de coloane - drop - sterge o tabela existenta - selectTables - extrage din lista de tabele existente un subset de tabele - join - combina doua tabele pe baza unei chei comune Pentru a gestiona operatii cu una sau mai multe tabele, vom folosi clasa: <code scala> case class Database(tables: List[Table]) { override def toString: String = ??? } </code> **3.1.**. Implementati functia create, care primeste numele unei tabele si creeaza o noua tabela doar daca numele tabelei nu exista deja in baza de date. Daca numele tabelei exista, functia va intoarce baza de date nemodificata. <code scala> def create(tableName: String): Database = ??? </code> **3.2.** Implementati functia drop, care primeste numele unei tabele si sterge tabela respectiva din baza de date. Daca numele tabelei nu exista in baza de date, functia va intoarce baza de date nemodificata. <code scala> def drop(tableName: String): Database = ??? </code> **3.3.** Implementati functia selectTables care primeste o lista de nume de tabele si extrage din baza de date doar acele tabele. Daca unul dintre numele de tabele nu exista in baza de date, functia va intoarce None. <code scala> def selectTables(tableNames: List[String]): Option[Database] = ??? </code> **3.4** Mai adăugati ceva (cautati voi functia de implementat :-D) la Table astfel incat sa putem accesa Rows din tabel folosind un index. Faceți același lucru si pentru Database ca sa putem accesa tabelele sale folosind index direct din numele unei instante. <code scala> val tabel = new Table("People", List( Map("id" -> "1", "name" -> "John", "age" -> "23", "CNP" -> "1234567890123"), Map("id" -> "2", "name" -> "Jane", "age" -> "25", "CNP" -> "1234567890124"), Map("id" -> "3", "name" -> "Jack", "age" -> "27", "CNP" -> "1234567890125"), Map("id" -> "4", "name" -> "Jill", "age" -> "29", "CNP" -> "1234567890126"), ))(1) // index aici val dbPeople = Database(List(tabel)) val tabel2 = dbPeople(0) // index aici </code> **3.5.** Implementati functia join, care primeste doua tabele si o coloana specifica pentru fiecare tabel. Aceasta functie va combina tabelele folosind coloanele indicate, rezultand intr-un nou tabel. * Cand valorile din coloanele folosite pentru combinatie sunt identice, se va retine o singura valoare din acele coloane. * Daca valorile difera, ele vor fi unite intr-un singur camp, separandu-le prin semnul ";", urmand ordinea in care tabelele sunt enumerate in apelul functiei. * Se considera ca valorile de tip sir de caractere gol ("") sunt echivalente cu NULL, adica acestea nu vor fi incluse daca exista o valoare specifica intr-o alta tabela. * In situatiile in care o linie este prezenta in tabelul A dar nu are corespondent in tabelul B, se vor completa campurile corespunzatoare din tabelul B cu sirul vid "". * Similar, daca o linieeste prezenta in tabelul B dar nu are corespondent in tabelul A, se vor completa campurile corespunzatoare din tabelul A cu sirul vid "". * Numele coloanei utilizate pentru join in tabelul final va fi preluat din primul tabel. * Se va intoarce eroare cand unul din tabele nu exista. Daca un tabel este gol, se va intoarce celălalt tabel. * Liniile din rezultat sunt in ordinea: linii ce au intrări in ambele tabele, linii doar in prima tabela, linii doar in a2a tabela. <hidden Exemplu> **Exemplu:** **Tabelul A** ^id^name^age^ | 1 | Ana | 20 | | 2 | Ion | 30 | | 4 | Maria | | **Tabelul B** ^id^city^job^age^ | 1 | Cluj | IT | 20 | | 2 | Iasi | HR | | | 3 | Buc | MKT | 40 | | 4 | Buc | | | **join("A", "id", "B", "id")** ^id^name^age^city^job^ | 1 | Ana | 20 | Cluj | IT | | 2 | Ion | 30 | Iasi | HR | | 3 | | 40 | Buc | MKT | | 4 | Maria| | Buc | | </hidden> <code scala> def join(table1: String, c1: String, table2: String, c2: String): Option[Table] = ??? </code> ==== Testare ==== ''Scalatest'' este o biblioteca de testare pentru Scala care suporta mai multe stiluri de scriere a testelor, inclusiv testarea traditionala unitara. ''Scalactic'' este o biblioteca destinata sa faciliteze scrierea de cod mai clar si mai intretinabil in Scala, utilizata in combinatie cu Scalatest pentru a imbunatati claritatea si precizia testelor. Pentru a rula testele utilizand aceasta configuratie, puteti folosi comanda de mai jos in terminal, de la radacina proiectului. Acest lucru va compila si executa toate testele definite in proiect care depind de Scalatest si Scalactic pentru a verifica corectitudinea codului. <code> sbt test </code> ===== Submisie arhiva ===== <note important> Veti incarca pe moodle o arhiva ce contine, in radacina acesteia, folderul ''src'' al proiectului vostru, fisierul ''build.sbt'' si un fisier text, intitulat ''ID.txt'' ce contine o singura linie, si anume id-ul vostru anonim (pe care il puteti gasi pe moodle la assignment-ul ''tokenID''). </note> Exemplu structura arhiva: <code> archive.zip |-src/ | |-main/ | | |-scala/ | | | | - ... <fisierele cu sursa scala> |-build.sbt |-ID.txt </code> ==== Puncatje ==== Pentru ca avem cam multe exercitii de implementat, găsiți mai jos un tabel cu punctajele grupate: WIP WIP WIP WIP ^ Parte a temei ^ Functionalitate ^ Punctaj ^ | Table | toString | 0.5 | | Table | insert | 1.5 | | Table | delete | 1.5 | | Table | sort | 1.5 | | Table | select | 1.5 | | Table | apply | 1.5 | | Table | filter | 4.5 | | Table | update | 4.5 | | **TABLE** | **TOTAL** | **17** | | Filter | Field | 4 | | Filter | Compound | 3 | | Filter | Not | 2 | | Filter | And | 2 | | Filter | Or | 2 | | Filter | Equal | 2 | | Filter | Any | 2 | | Filter | All | 2 | | **FILTER** | **TOTAL** | **22** | | Database | create | 1.5 | | Database | drop | 1.5 | | Database | selectTables | 1.5 | | Database | indexing | 1.5 | | Database | join | 10 | | ''TEMA 2'' | ''TOTAL'' | ''100'' |