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 2023 ====== <note important> **DEADLINE TODO ** * 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 puncate. * 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>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 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 '-') </note> ====Scopul temei==== 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. * Imutabilitatea: Scala 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 pure: Scala 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. Tocmai de accea, o modalitate de a aplica cunostiintele de programare functionala dobandite pana acum este aceea de a manipula date tabelare. Cerintele temei va vor ghida pentru a implementa operatii uzuale pe tabele, folosind tipuri de date definite de voi pentru a facilita implementarea. 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. **TODO dezvolta in mai multe cuvinte** ==== Conventii ==== Pentru reprezentarea tabelelor din Query Language, se vor folosi urmatoarele conventii: * numele coloanelor este reprezentat sub forma de lista * fiecare linie a tabelului e reprezentata ca o mapare intre numele colanei si valoarea corespunzatoare <code scala> // a row contains the column name and the value type Row = Map[String, String] // a line is just a row without column names type Line = List[String] </code> ====1. Parsarea datelor==== Pentru inceput, trebuie sa ne definim o clasa prin care sa modelam ce este un tabel. Vom implementa clasa ''Table'' ce retine numele coloanelor si informatia din tabel. <code scala> case class Table (column_names: Line, tabular: List[List[String]]) { ??? } </code> 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. **1.1.** Suprascrieti metoda ''toString'' a unui tabel, astfel incat sa puteti reprezenta un tabel in forma csv. Liniile vor fi separate prin ''\n'', iar coloanele vor fi delimitate prin '',''. (TODO: escaping ghilimele) **1.2.** Definiti functia ''apply'' intr-un companion object al clasei ''Table''. Functia trebuie sa parseze un sir de caractere si sa returneze un tabel, daca acest lucru este posibil. <code scala> def apply(s: String): Table = ??? </code> ====2. Functii de procesare a datelor==== In continuare, vom implementa operatii pe tabele. **2.1.** Implementati functia ''select'' care selecteaza valorile din tabel asociate unor coloane date ca input. Daca operatia nu este posibila/permisa, se va marca aces lucru folosind ''None''. <code scala> def select(columns: Line): Option[Table] = ??? </code> **2.2.** Implementati functia ''filter'' care aplica un filtru pe liniile din tabel, fiind pastrate doar acele linii care verifica conditia. (Hint: pentru a intelege cine este Filter, vezi sectiunea 3) <code scala> def filter(cond: FilterCond): Option[Table] = ??? </code> Pentru acest exercitiu, este nevoie sa implementam si filtrele ce se aplica pe o linie din tabel. Astfel, exista: * filtre logice --> aplica functiile logice AND si OR intre 2 filtre * filtre asociate unei coloane --> decid pastrarea/eliminarea unei linii pe baza unui predicat ce primeste numele unei coloane <code scala> trait FilterCond { def &&(other: FilterCond): FilterCond = And(this,other) def ||(other: FilterCond): FilterCond = Or(this,other) // fails if the column name is not present in the row 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 And(f1: FilterCond, f2: FilterCond) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } case class Or(f1: FilterCond, f2: FilterCond) extends FilterCond { override def eval(r: Row): Option[Boolean] = ??? } </code> **2.3** 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. <code scala> def newCol(name: String, defaultVal: String): Table = ??? </code> **2.4.** Implementati functia ''merge'' care uneste 2 tabele. Identificarea liniilor ce trebuie combinate se va face folosing coloana comuna ''key''. Daca exista suprapuneri intre valori, se vor pastra amandoua 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. <code scala> def merge(key: String, other: Table): Option[Table] = ??? </code> <hidden Exemplu> <code> TABEL 1: PL Functional Description Haskell yes nice Scala yes cool Python yes good TABEL 2: PL OO Description Scala yes cool Python yes whoa MERGE intre TABEL 1 si TABEL 2 cu cheia PL: PL Functional OO Description Haskell yes "" nice Scala yes yes cool Python yes yes good,whoa </code> </hidden> ====3. Query Language ==== Obiectivul acestui task este acela de a: * 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 Veti implementa un Query Language ce reprezinta un API pentru o varietate de transformari de tabele, deja implementate anerior ca functii. Un ''Query'' este o secventa/ combinatie de astfel de transformari. Query-uri posibile: * ''Value'' * reprezentarea de baza a unui tabel sub forma de Query * ''Select'' * selecteaza o lista de coloane dintr-un tabel dat * esueaza daca se doreste o coloana ce nu se gaseste in tabel * ''NewCol'' * creeaza o noua coloana cu nume dat intr-un tabel, completand coloana cu o valoare default * ''Merge'' * combina 2 tabele pe baza unei chei comune * operatia e posibila doar daca cheia se gaseste in ambele tabele * ''FIlter'' * aplica un filtru pe liniile tabelei <code scala> trait Query { def eval: Option[Table] } case class Value(t: Table) extends Query { override def eval: Option[Table] = ??? } case class Select(columns: Line, target: Query) extends Query { override def eval: Option[Table] = ??? } case class Filter(condition: FilterCond, target: Query) extends Query{ override def eval: Option[Table] = ??? } case class NewCol(name: String, defaultVal: String, target: Query) extends Query{ override def eval: Option[Table] = ??? } case class Merge(key: String, t1: Query, t2: Query) extends Query { override def eval: Option[Table] = ??? } </code> In finalul acestei teme, vom scrie propriul nostru Query, folosind tabelele Functional, Object-Oriented si Imperative, ce contin doar 3 coloane: "Language", "Original purpose", "Other paradigms". **3.1.** Creati tabelul ''Table.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 **3.2** Eliminati limbajele al caror scop original este "Application" si sunt "concurrent". Rezultatul se va numi ''Table.programmingLanguages2''. **3.3** Selectati doar coloanele "Language", "Object-Oriented" and "Functional" din tabelul obtinut anterior. Rezultatul se va numi ''Table.programmingLanguages3''.