Table of Contents

Tema 2 PP 2025

Schelet: skel_tema2.zip
DEADLINE 4 Mai 2025
  • 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.
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!)

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:

type Row = Map[String, String] // nume_coloana - valoare
type Tabular = List[Row]

Funcții utile pentru lucrul cu Map

Funcții utile pentru lucrul cu Map

let map = Map(1 -> 2, 3 -> 4): Map[Int, Int]
  • Adauga o noua pereche cheie-valoare
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
  • Eliminarea unei perechi cheie-valoare
map - (3 -> 4)  // Map(1 -> 2)
  • Accesarea valorii asociate unei chei
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
  • Functii de orin superior
map mapValues (x => x + 5)  // Map(1 -> 7, 2 -> 9)
map filterKeys (x => x <= 2)  // Map(1 -> 2)
  • Combinarea a doua map-uri
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)

Clasa Table

Vom defini un tabel ca o clasa care are ca atribute numele tabelei tableName si datele tableData.

case class Table (tableName: String, tableData: Tabular) {
  def header: List[String] = ???
  def data: Tabular = ???
  def name: String = ???
}

1.1 Definiti metoda toString care returneaza tabelul in forma CSV.

override def toString: String = ???

1.2 Definiti metoda fromCSV care returneaza un tabel dintr-un string de tip CSV.

def fromCSV(CSV: String): Table = ???

1.3 Definiti operatia de inserare a unei linii in tabel.

def insert(row: Row): Table = ???

1.4 Definiti operatia de stergere a tuturor liniilor exact egale cu cea primita ca parametru.

def delete(row: Row): Table = ???

1.5 Definiti operatia de sortare a liniilor din tabel dupa o anumita coloana. Functia are un parametru optional ce determina ordinea in care trebuiesc sortate. Mai mult despre parametrii optionali puteti gasi aici.

def sort(column: String, ascending: Boolean = true): Table = ???

1.6 Definiti functia select care primeste o lista de stringuri si returneaza un nou obiect de tip Table ce contine doar coloanele specificate.

def select(columns: List[String]): Table = ???

1.7 Definiti functia cartesianProduct care primeste un alt tabel si returneaza un nou obiect de tip Table ce contine produsul cartezian al celor doua tabele.

def cartesianProduct(otherTable: Table): Table = ???

1.8 Definiti functia join, care primeste un alt tabel si o coloana specifica pentru fiecare tabel. Aceasta functie va combina tabelele folosind coloanele indicate, rezultand intr-un nou tabel.

Exemplu

Exemplu

Exemplu:

Tabelul A

idnameage
1 Ana 20
2 Ion 30
4 Maria

Tabelul B

idcityjobage
1 Cluj IT 20
2 Iasi HR
3 Buc MKT 40
4 Buc

join(“A”, “id”, “B”, “id”)

idnameagecityjob
1 Ana 20 Cluj IT
2 Ion 30 Iasi HR
3 40 Buc MKT
4 Maria Buc
def join(other: Table)(col1: String, col2: String): Table = ???

1.9 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.

def apply(name: String, s: String): Table = ???

Filtre peste Tabele

 <filter> ::= 
    <filter> && <filter> | 
    <filter> || <filter> |
    <filter> == <filter> |
    !<filter> |
    any [ <filter> ] |
    all [ <filter> ] |
    operation [ <filter> ]

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:

  1. Field - reprezinta o conditie de filtrare pe un camp al tabelei. Aceasta conditie este satisfacuta daca valoarea de pe coloana specificata respecta predicatul.
  2. Compound - reprezinta o conditie de filtrare compusa din mai multe conditii. Aceasta conditie este satisfacuta daca toate conditiile din lista conditions sunt satisfacute.
  3. Not - reprezinta negarea unei conditii de filtrare.
  4. And - reprezinta conjunctia a doua conditii de filtrare.
  5. Or - reprezinta disjunctia a doua conditii de filtrare.
  6. Equal - reprezinta o conditie de egalitate intre doua conditii de filtrare.
  7. Any - reprezinta o conditie de filtrare care este satisfacuta daca cel putin una dintre conditiile din lista este satisfacuta.
  8. All - reprezinta o conditie de filtrare care este satisfacuta daca toate conditiile din lista sunt satisfacute.
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] = ???
}

2.2. Pentru a simplifica definirea conditiilor de filtrare, vom defini cateva operatori in cadrul traitului FilterCond care sa ne permita sa scriem cod mai concis. Vom folosi urmatorii operatori ce extind clasa FilterCond:

  1. == - pentru a verifica egalitatea a doua conditii de filtrare.
  2. && - pentru a face conjunctia a doua conditii de filtrare.
  3. || - pentru a face disjunctia a doua conditii de filtrare.
  4. ! (operator unar) - pentru a nega o conditie de filtrare.
def ==(other: FilterCond) = ???
def &&(other: FilterCond) = ???
def ||(other: FilterCond) = ???
def unary_! = ??
// Puteti sa adaugati mai multi operatori :)

2.3. Definiti operatia de filtrare a liniilor din tabel care respecta o anumita conditie.

def filter(f: FilterCond): Table = ???

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.

def update(f: FilterCond, updates: Map[String, String]): Table = ???

==== Op eratii cu una sau mai multe Tabele

O baza de date contine mai multe tabele, pe care putem aplica o serie de operatii:

  1. insert - creaza o noua tabela cu nume unic si o lista de coloane
  2. update - actualizeaza informatiile unei tabele deja existente
  3. delete - sterge o tabela existenta
  4. selectTables - extrage din lista de tabele existente un subset de tabele

Pentru a gestiona operatii cu una sau mai multe tabele, vom folosi clasa:

case class Database(tables: List[Table]) {
  override def toString: String = ???
}

3.1. Implementati functia insert, 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.

def insert(tableName: String): Database = ???

3.2 Implementati functia update care primeste numele unei tabele si o tabela noua. Functia trebuie sa inlocuiasca tabela veche din baza de date cu noua tabela. Daca numele tabelei nu exista in baza de date, functia va intoarce baza de date nemodificata.

def update(tableName: String, newTable: Table): Option[Database] = ???

3.3 Implementati functia delete, 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.

def delete(tableName: String): Database = ???

3.4 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.

def selectTables(tableNames: List[String]): Database = ???

3.5 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.

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

==== Que ry ====

Testati structurile folosite executand niste query-uri, ce ar putea fi operatii necesare intr-un backend. Query-urile ar trebuie sa fie one-linere, fara sa folositi val intermediare, dar puteti defini functii auxiliare (pentru filtre)

4.1 Selectati din tabela Customers toti cei care au peste X ani si locuiesc intr-un oras din lista primita. Returnati raspunsul ordonat dupa ID.

def query_1(db: Database, ageLimit: Int, cities: List[String]): Option[Table] = {
...
} 

4.2 Selectati din tabela Orders toate comenzile de dupa o data care nu au fost procesate de un angajat anume. Returnati doar OrderID si Cost, ordonat dupa Cost descrescator.

def query_2(db: Database, date: String, employeeID: Int): Option[Table] = {
...
}

4.3 Selectati din tabela Orders cine a facut comenzii cu Cost peste o valoare data. Returnati OrderID, EmployeeID si Cost, ordonat dupa EmployeeID crescator.

def query_3(db: Database, minCost: Int): Option[Table] = {
...
}

==== Te stare ====

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.

 sbt test  

Submisie arhiva

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).

Exemplu structura arhiva:

archive.zip
|-src/
| |-main/
| | |-scala/
| | | | - ... <fisierele cu sursa scala>
|-build.sbt
|-ID.txt

Punctaje

Pentru ca avem cam multe exercitii de implementat, găsiți mai jos un tabel cu punctajele grupate:

Parte a temei Functionalitate Punctaj
Table toString 3
Table fromCSV 3
Table insert 3
Table delete 3
Table sort 3
Table select 3
Table cartesianProduct 7.5
Table join 7.5
Table apply 4
Table filter 4
Table update 4
TABLE TOTAL 45
Filter Field 3
Filter Compound 3
Filter Not 3
Filter And 3
Filter Or 3
Filter Equal 3
Filter Any 3
Filter All 3
Filter operator == 1.5
Filter operator && 1.5
Filter operator || 1.5
Filter operator ! 1.5
FILTER TOTAL 30
Database insert 3
Database update 3
Database delete 3
Database selectTables 3
Database indexing 3
DATABASE TOTAL 15
Query query1 3
Query query2 3
Query query3 4
QUERY TOTAL 10
TEMA 2 TOTAL 100