Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
pp:2023:hw2 [2023/04/10 03:08]
sorin.mateescu [Task 2: JOIN query]
pp:2023:hw2 [2024/04/03 11:47] (current)
alexandra.udrescu01 [Tabele]
Line 3: Line 3:
 <note important>​ <note important>​
  
-**DEADLINE ​''​ TODO '' ​**+**DEADLINE ​28 aprilie 2023**
  
-  * Temele trebuie ​trimise ​pe curs.upb.ro, ​în assignment-ul numit ''​Tema 2''​. +  * Temele trebuie ​submise ​pe curs.upb.ro, ​in assignment-ul numit ''​Tema 2''​. 
-  * Temele ce nu sunt acceptate de validatorul de arhive **NU** vor fi punctate+  * Temele ce nu sunt acceptate de validatorul de arhive **NU** vor fi puncate
-  * Vă sugerăm ​ca, inainte de trimitere, sa testați arhiva voastra ​cu validatorul de arhive.+  * Va sugeram ​ca dupa ce ati incarcat o arhiva, sa o descarcati si sa o testati ​cu validatorul de arhive. 
 +</​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>​
 +<​note>​Scheletul se poate găsi la: {{:​pp:​2023:​pp-tema2-skeleton.zip|}}</​note>​
 +<​note>​
 +Validatorul de arhive se poate găsi la: {{:​pp:​2023:​archive_validator_hw2.zip|}}
 +\\
 +Formatul arhivelor este:
 +  * util/​Util.scala
 +  * Table.scala
 +  * TestTables.scala
 +  * ID.txt - acest fisier va contine o singura linie, formata din ID-ul unic al fiecarui student
 +Numele arhivelor trebuie sa fie de forma **<​Nume>​_<​Prenume>​_<​Grupa>​_T2.zip** (daca aveti mai multe prenume sau nume, le puteti separa prin '​-'​)
  
-**TODO**: ar mai trebui sa avem partea de inserare, stergere avand in vedere ca o sa avem doar query-uri de tip SELECT si JOIN?+</​note>​
  
-In aceasta tema veti implementa un **query engine** minimal, care operează pe tabele, pentru a facilita selectarea, inserarea, stergerea datelor existente dintr-un **tabel**.+====Scopul temei====
  
-**TODO**: as propune urmatoarea descriere:+/* 
 +Scala este un limbaj de programare care a fost dezvoltat initial pentru a fi utilizat in proiectele de mare scalabilitate si in special pentru a procesa date in big data. Acesta are o serie de caracteristici care il fac foarte util pentru a lucra cu big data. 
 +  ​Performanta:​ Scala este un limbaj de programare puternic si rapid, care poate procesa un volum mare de date intr-un timp scurt. Acest lucru il face foarte util in proiectele de big data care necesita performanta ridicata. 
 +  ​Scalabilitate:​ Scala este conceput pentru a fi scalabil si poate fi utilizat in proiecte mari care necesita procesarea a milioane sau chiar miliarde de inregistrari. 
 +  ​ImutabilitateaScala pune accent pe imutabilitate,​ ceea ce inseamna ca datele nu se pot schimba dupa ce au fost create. Acest lucru face ca codul sa fie mai usor de inteles si mai usor de gestionat in proiectele de big data. 
 +  * Functii pureScala pune accentul pe functiile pure, care nu au efecte secundare si nu modifica starea sistemului. Aceste functii sunt utile in proiectele de big data, deoarece putem fi siguri ca nu vom modifica accidental datele in mod neasteptat.
  
-În această temă veți implementa un **query engine** minimalcare operează pe **tabele**, pentru ​facilita filtrarea(**SELECT**) si alăturarea lor(**JOIN**).+Tocmai de acceao modalitate de aplica cunostiintele de programare functionala dobandite pana acum este aceea de a manipula date tabelare.  
 +*/
  
-Având aceste funcționalități,​ veti putea implementa ​**funcții statistice**care să lucreze pe acestea.+Cerintele temei va vor ghida pentru a implementa ​operatii uzuale pe tabelefolosind tipuri de date definite de voi pentru a facilita implementarea.
  
-==== Ce este un tabel? ====+In final, veti obtine ​un Query Language ce ofera posibilitatea de a aplica operatii complexe si de a manipula datele din tabele intr-un mod flexibil si usor de extins cu noi functionalitati.
  
-Formatul unui tabel pentru acesta tema va fi CSV. 
  
-De exemplu, pentru tabela **people**, un exemplu de conținut ar fi următorul:+==== Tabele ====
  
-<code csv> +Un tabel este in esenta o matrice ce contine elemente de tip ''​String''​alaturi de cate un **nume** asociat fiecarei coloanenecesar pentru a descrie cu usurinta operatii peste acestea. In aceasta temavom folosi doua reprezentari pentru o linie a unei matricifiecare potrivita in contexte diferite: 
- +  * ''​Line''​ - este pur si simplu o lista cu valorile liniei corespunzatoare. 
-id,name,age,sex,positionId +  * ''​Row''​ - este un Map in care asociaza numelui coloanei valoarea de pe linia respectiva.
-1,​Turing,​43,​M,​2xf +
-2,​Curry,​27,​M,​9cc +
-3,​Radia,​29,​F,​2xf,​ +
-4,​Chomsky,​94,​M,​7af+
  
 +<code scala>
 +type Row = Map[String, String]
 +type Line = List[String]
 </​code>​ </​code>​
  
-Pe prima linie sunt definite denumirile coloanelor ​(id,​name,​age,​sex,​positionId)+====1. Tipul de date Table si parsarea datelor ​(10p====
-Pe următoarele linii se află intrările din tabelă.+
  
-Pentru ​a putea face operații pe o tabelăaceasta va trebui ​sa aibe reprezentare +Pentru ​inceputtrebuie ​sa definim ​clasa prin care sa modelam ce este un tabel.
-**internă** ​care să ajute acest fapt.  +
- +
-O soluție simplă ​este ca aceasta să fie transformată +
-dintr-un simplu String **CSV** într-o **matrice** de String-uri, de următorul tip:+
  
 +Vom implementa clasa ''​Table''​ in acest sens:
 <code scala> <code scala>
- +class Table (column_names:​ Line, tabular: ​List[List[String]]) {  
-type Table List[List[String]] +    ??? 
 +}
 </​code>​ </​code>​
  
-==== Tipuri ​de query-uri ====+Pentru a putea rula checkerul, este nevoie sa putem parsa input si output. Prin urmare, avem nevoie ​de o functie de citire si una de afisare a unui tabel.
  
-  ​* **SELECT**: are rolul de filtra o tabelă pe baza unei condiții date legată de o coloanăprecum si alegerea coloanelor care vor participa în rezultat+**1.1. (5p)** Suprascrieti metoda ''​toString'' ​unui tabel, ​astfel incat sa puteti reprezenta un tabel in forma csv
-  * **JOIN**: combină înregistrări din două tabele pe baza unor coloane comune.+Liniile vor fi separate prin ''​\n'',​ iar coloanele vor fi delimitate prin '',''​.
  
-Cu ajutorul lor se vor putea implementa ​**funcții statistice**, cu ajutorul cărora se pot număra linii specifice din tabelă, se poate face media unui atribut etc.+**1.2. (5p)** Definiti functia ''​apply''​ intr-un companion object al clasei ''​Table''​. Functia trebuie sa parseze un sir de caractere si sa returneze un tabel. 
 +<code scala> 
 +def apply(s: String): Table = ??? 
 +</​code>​
  
 +====2. Functii de procesare a datelor (50p)====
  
 +In continuare, vom implementa operatii pe tabele, ca **membri** ai clasei ''​Table''​.
  
-==== Task 1: SELECT query ==== +**2.1. (5p)** Implementati functia ''​select''​ care selecteaza valorile din tabel asociate unor coloane ​date ca inputDaca operatia face referire la coloane inexistenteea va intoarce ''​None''​. 
- +<​code ​scala
-În prima etapă, va trebui implementată funcționalitate de **SELECT**. +def select(columns:​ Line): Option[Table] = ???
- +
-Cererea de tip **SELECT** lucrează pe o singură ​tabelă, având rolul de a filtra linile din aceasta, precum și de a alege ce coloane ​se vor afla în rezultat. +
- +
-<hidden Mai multe explicații>​ +
- +
-Pentru a avea o înțelegere mai bună a acesteiaurmează un exemplu din **SQL*plus**:​ +
- +
-<​code ​sql+
- +
-SELECT name, age +
-FROM people +
-WHERE age < 30; +
 </​code>​ </​code>​
  
-    ​Pe prima linie este clauza //SELECT//, unde se specifică coloanele care se vor afla în rezultat. +**2.2(20p)** Implementati functia ''​filter''​ care aplica un filtru pe liniile din tabelfiind pastrate doar acele linii care verifica conditiaDaca conditia ​de filtrare ​face referiri la coloane inexistenteoperatia ​va intoarce ''​None''​.
- +
-    ​Pe a doua linie este clauza //FROM//unde se află tabela pe care va avea loc operația descrisă. +
- +
-    * Pe ultima linie se află clauza //WHERE//, urmată de o condiție ​de filtrare ​a linilor tabelei **people**. +
- +
-În concluzierezultatul ​va fi o nouă tabelă, formată doar din persoanele mai tinere de 30 ani, având ca informații numele și vârsta. +
- +
-</​hidden>​ +
- +
- +
-Acesta va avea următoarea signatură:​ +
 <code scala> <code scala>
-def select(tableTable, columnsToExtractList[String], columnToCompare:​ String, condition: (String ​=> Boolean)): Table+def filter(condFilterCond)Option[Table] = ???
 </​code>​ </​code>​
  
-Fiecare parametru are următoarea semnificație:​ +Pentru acest exercitiueste nevoie sa implementam si filtrele ce se aplica pe o linie din tabel. Definim un filtru recursiv, astfel
- +<​code> ​ 
-    * **table**: tabela primităparsată ​din formatul CSV +<​filter> ​::= <​filter>​ && <​filter>​ | <​filter>​ || <​filter>​ | (<​column_name>​ <​predicate>​) 
- +</​code>​
-    * **columnsToExtract**lista cu denumirea coloanelor care vor fi prezente în rezultat +
- +
-    * **columnToCompare**coloana pe baza căreia va trebuie sa se facă filtrarea +
- +
-    * **condition**funcția de filtrare a tabelei +
- +
-Valoarea de **return** este tot o tabelă parsată, rezultată din filtrarea si proiecția celei primite ca input.+
  
-** Exemplu: **+Unde ''<​column_name>''​ este un nume de coloana, iar <​predicate>​ este un predicat ce poate sau nu sa fie satisfacut de valoarea de la coloana respectiva.
  
 <code scala> <code scala>
 +trait FilterCond {
 +  // these are useful for the intuitive infix notation
 +  // e.g. the following expression is a filter condition:
 +  // Field("​PL",​ x=>true) && Field("​PL",​ x=> false)
 +  def &&​(other:​ FilterCond):​ FilterCond = ???
 +  def ||(other: FilterCond):​ FilterCond = ???
 +  ​
 +  // fails if the column name is not present in the row
 +  def eval(r: Row): Option[Boolean]
 +  ​
 +}
  
-// pentru acest exemplu se va folosi +case class Field(colName:​ String, predicate: String => Boolean) extends FilterCond{ 
-// tabela din prima secțiune ​(people)+  ​override def eval(r: Row): Option[Boolean] = ??? 
 +}
  
-select(peopleList("​name",​ "​age"​), "​age", ​(ageString ​=> age.toInt < 30))+case class And(f1: FilterCondf2: FilterCondextends FilterCond { 
 +  override def eval(rRow): Option[Boolean] ​??? 
 +}
  
 +case class Or(f1: FilterCond, f2: FilterCond) extends FilterCond {
 +  override def eval(r: Row): Option[Boolean] = ???
 +}
 </​code>​ </​code>​
  
-Rezultatul va trebui să fie tot o tabelă parsată, având linile din  +**2.3. (5p)** Implementati functia ''​newCol''​ ce adauga o noua coloana la finalul unui tabel, fiecare intrare in acea coloana asociata unei linii fiind completata ​cu o valoare default
-**people** cu coloana **age** mai mică decât 30De asemenea, se vor păstra doar +<​code ​scala
-coloanele **name** și **age**: +def newCol(name: StringdefaultVal: String): Table = ???
- +
-Reprezentarea rezultatului în format CSV: +
- +
-<​code ​csv+
- +
-name,age +
-Curry,27 +
-Radia,29 +
 </​code>​ </​code>​
  
- +**2.4. (20p)** Implementati functia ''​merge''​ care uneste 2 tabele. Identificarea liniilor ce trebuie combinate se va face folosind coloana comuna ''​key''​. Daca exista suprapuneri intre valorile ​asociate unei chei in cele doua tabelese vor pastra amandoua valorile (daca sunt diferite) sub forma unui singur sir de caractere (cele doua intrari vor fi separate prin '';''​)iar acolo unde nu exista corespondent in unul dintre tabele, coloanele ramase necompletate vor fi umplute cu un sir de caractere gol. Daca coloana ''​key''​ nu exista in vreunul ​din tabele, se va intoarce ''​None''​.
-==== Task 2: JOIN query ==== +
- +
- +
-Query-ul ​**join** combină două tabele, alăturând coloanele din acestea, be baza egalității între ​valorile ​din coloanele cu semnificație comună, specificate în cereresau pe baza unui predicat mai complexcare primește valori ​din cele două coloane. +
 <code scala> <code scala>
-// column1 este o coloană din primul tabel +def merge(keyStringother: Table): ​Option[Table] = ???
-// column2 este o coloană din al doilea tabel +
-// condition este predicatul în baza căruia se realizează operația +
-def join(table1Tabletable2: Table, column1: String, column2: String, condition: (String, String) => Boolean): Table+
 </​code>​ </​code>​
  
-Exemplu:+<​hidden ​Exemplu
 +<​code>​ 
 +TABEL 1: 
 +PL      Functional Description 
 +Haskell yes        nice 
 +Scala   ​yes ​       cool 
 +Python ​ yes        good
  
-Folosim tabela people, definită anterior, împreună cu tabela companies:+TABEL 2: 
 +PL      OO         ​Description 
 +Scala   ​yes ​       cool 
 +Python ​ yes        whoa
  
-<​code>​ +MERGE intre TABEL 1 si TABEL 2 cu cheia PL: 
-companyId,​jobId,​roleType,​isFilled +PL      Functional Description OO 
-1,7,2XF,yes +Haskell ​yes        ​nice ​       ""​ 
-2,3,2XF,no +Scala   ​yes ​       cool        yes 
-3,5,7af,yes+Python ​ yes        good;​whoa ​  yes
 </​code>​ </​code>​
 +</​hidden>​
  
-<code scala> +====3. Query Language (40p)====
-// O cerere de join între cele două poate arăta astfel:+
  
-join(people,​ companies, "​positionId",​ "​roleType",​ (pString, rString) => p.equalsIgnoreCase(r)) +Obiectivul acestui task este acela de a: 
-</​code>​+  * permite programatorilor sa **combine** functiile de procesare a tabelelor intr-un mod flexibil 
 +  * crea o **separare** intre ceea ce fac functiile si modul in care sunt implementate --> acest lucru este folositor din mai multe motive
 +    * se pot integra **functionalitati noi** foarte usor 
 +    * debug, testare
  
-Rezultatul conține toate coloanele din primul tabel, urmate ​de toate coloanele din al doilea tabelmai puțin column2:+**3.0. (25p)** Veti implementa un Query Language ce reprezinta un API pentru o varietate ​de transformari de tabeledeja implementate anerior ca functii. Un ''​Query''​ este o secventa/ combinatie de astfel de transformari.
  
 +Query-urile sunt descrise recursiv astfel:
 <​code>​ <​code>​
-id,​name,​age,​sex,​positionId,​companyId,​jobId,​isFilled +<​query>​ ::=  <​table> ​                              // query atomic - reprezinta tabelul initial pe care se va realiza query-ul. ​   
-1,Turing,​43,​M,​2xf,​1,​7,​yes +             | Select <​column_list>​ <​query> ​       //​selecteaza o lista de coloane dintr-un tabel dat
-1,​Turing,​43,​M,​2xf,​2,​3,​no +                                                   //esueaza daca se doreste o coloana ce nu se gaseste in tabel 
-3,Radia,​29,​F,​2xf,​1,​7,​yes +             | NewCol <​column_name>​ <​query> ​       //creeaza o noua coloana cu nume dat intr-un tabelcompletand coloana cu o valoare default 
-3,​Radia,​29,​F,​2xf,​2,3,no +                                                   //operatia e posibila doar daca cheia se gaseste in ambele tabele 
-4,​Chomsky,​94,​M,​7af,​3,​5,​yes+             | Merge <​column_name>​ <​query>​ <​query>​ //​combina ​tabele pe baza unei chei comune 
 +                                                   //​operatia e posibila doar daca cheia se gaseste in ambele tabele 
 +             | Filter <​filter_cond>​ <​query> ​       //aplica un filtru pe liniile tabelei 
 </​code>​ </​code>​
  
-==== Task 3: Statistici ==== +Spre exemplu, pentru ​a evalua query-ul: ''​Select "​PL"​ <​Q>'',​ se va evalua intai query-ul ''<​Q>''​ apoi se va efectua operatia //Select// pe rezultatul intors ​de ''​Q''​. 
-În această etapăvom folosi etapele anterioare ​pentru ​realizarea ​de statistici pentru datele existente. Statisticile vor reprezenta, în mare, o singură valoare care corespunde răspunsului unei cerințe.+Scheletul de implementare pentru Query-uri se gaseste mai jos:
  
-**Exemplu**:+<code scala> 
 +trait Query { 
 +  def evalOption[Table] 
 +}
  
-''​Care este media vârstei persoanelor din departamentul IT?''​+case class Value(t: Table) extends Query { 
 +  override def eval: Option[Table] = ??? 
 +}
  
-Funcția asociată acestei întrebări trebuie să se folosească de etapele anterioare +case class Select(columnsLinetarget: Query) extends Query { 
-  * folosirea unui join query pentru a prelua persoanele din fiecare departament+  ​override def eval: Option[Table] = ??? 
-  ​* urmată de o procesare a listei rezultat pentru a returna doar oamenii din departamanetul ''​IT'',​ +}
-  * urmată de o procesasre pentru calcularea mediei vârstei.+
  
-**TODO**schimbare pseudocod astfel incat sa se conformeze la noua signatura a functilor+case class Filter(conditionFilterCond, target: Query) extends Query{ 
 +  override def eval: Option[Table] = ??? 
 +}
  
-<​hidden>​ +case class NewCol(name:​ String, defaultVal: String, target: Query) extends Query{ 
-pseudocod pentru realizare:+  ​override def evalOption[Table] = ??? 
 +}
  
-<code scala> +case class Merge(key: Stringt1: Queryt2: Queryextends Query { 
-val join = joinQuery("JOIN positionspersons ON idpositionId"​+  ​override def eval: Option[Table] ​??? 
-val itPeople ​join.filter( x => x._1 == "​IT"​ ).head._2 +}
-val sumAge = itPeople.map(x => x.age).sum +
-return sumAge / itPeople.size+
 </​code>​ </​code>​
-</​hidden>​ 
  
-**Listă statistici de implementat**:​ +In finalul acestei teme, vom scrie propriul nostru Query, folosind tabelele Functional, Object-Oriented si Imperativece contin doar 3 coloane: "​Language"​"​Original purpose",​ "Other paradigms"​.
-  ​Care este **media** vârstei persoanelor de pe  //<​department_name>//?​ +
-  - Care este vârsta **medie** a persoanelor care au terminatîn mediemai mult de //<​percentage>//​ din task-uri? +
-  - **Câți** bărbați căștigă mai mult de //<​monthly_salary>//​ pe lună? +
-  - Care este **raportul** dintre numărul de femei și numărul de bărbați care lucrează în cadrul departamentului de //<​department_name>//?​ +
-  - Care este bonusul anual **mediu** al persoanelor mai tinere de //<​age>//​ ani?+
  
-==== Alte informații ====+**3.1. (5p)** Creati tabelul ''​TestTables.programmingLanguages1''​ combinand functiile ''​newCol''​ si ''​merge'':​ 
 +  * creati o noua colana in fiecare tabel ce contine tipul limbajului, avand acelasi nume ca tabela si valoare default "​Yes"​ 
 +  * aplicati merge intre noile tabele dupa numele limbajelor de programare
  
-<note warning>​ +**3.2. (5p)** Păstrați doar limbajele al caror scop original ​este "​Application" ​si sunt "​concurrent"​. Rezultatul ​se va numi ''​TestTables.programmingLanguages2''​.
-**Folosiți un stil de programare funcționalNU se vor accepta:** +
-  * **Efecte laterale** (de exemplu modificarea parametrilor dați ca input la funcție) +
-  * Folosirea colecților aflate în **scala.collection.mutable** +
-  * **var** (**val** ​este ok!) +
- +
-</​note>​ +
-<​note>​Scheletul se poate găsi la: {{''​ TODO ''​}}</​note>​ +
-<​note>​ +
-Validatorul de arhive ​se poate găsi la: {{ ''​TODO''​}} +
-\\ +
-Formatul arhivelor este: +
-  * ''​ TODO ''​ +
-  * ID.txt - acest fișier va conține o singură linie, formată din ID-ul unic al fiecărui student +
-Numele arhivelor trebuie să fie de forma **<​Nume>​_<​Prenume>​_<​Grupa>​_T2.zip** (daca aveti mai multe prenume sau nume, le puteți separa prin '​-'​) +
- +
-</​note>​+
  
 +**3.3. (5p)** Selectati doar coloanele "​Language",​ "​Object-Oriented"​ si "​Functional"​ din tabelul obtinut anterior. Rezultatul se va numi ''​TestTables.programmingLanguages3''​.