Differences

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

Link to this comparison view

sd-ca:teme:tema3-2021 [2021/05/13 00:41]
gabriel_danut.matei
sd-ca:teme:tema3-2021 [2021/05/30 19:42] (current)
gabriel_danut.matei
Line 1: Line 1:
-<​hidden>​ +====== Tema Rope data structure ​======
-====== Tema Count-distinct problem ​======+
  
 ** Responsabili:​ ** ** Responsabili:​ **
Line 6: Line 5:
   *[[matei.danut.dm@gmail.com|Dănuţ Matei]]   *[[matei.danut.dm@gmail.com|Dănuţ Matei]]
  
-** Data publicării : 26 martie, ora: 21:00 **+** Data publicării : 13 mai, ora: 23:55 **
  
-** Deadline: ​17 aprilie, ora 23:55 **+** Deadline: ​3 iunie, ora 23:55 **
  
 == Modificări şi actualizări ==  == Modificări şi actualizări == 
-  * **27 martie, ora 01:30** - adăugat clarificari in sectiunea **Introducere** +  * **21 mai, ora 23:20** - actualizat secțiunea ​**Q&A** ca urmare a întrebărilor de pe **Forum** 
-  * **27 martie, ora 21:05** - adăugat **checker** in **enunt** si pe **vmchecker** +  * **30 mai, ora 16:30** - configurat tema pe **vmchecker** 
-  * **27 martie, ora 23:40** - modificat fisier ​**Makefile** din **checker** +  * **30 mai, ora 19:42** - actualizat secțiunea ​**Q&A** ca urmare ​întrebărilor ​de pe **Forum**
-  * **29 martie, ora 16:00** - adăugat precizari in **cerinta II** legate ​de **dimensiunea Hashtable-ului** +
-  * **29 martie, ora 18:40** - adăugat corectare in **cerinta III** legata de **definitia lui m** +
-  * **29 martie, ora 19:30** - modificat fisier **check.sh** din **checker**;​ acum partea de **valgrind** are comportamentul corect pentru toate cerintele +
-  * **3 aprilie, ora 19:10** - reformulat ​**cerinta III** pentru ​o face mai usor de inteles +
-  * **11 aprilie, ora 12:05** - actualizat ​**deadline**+
  
- 
- 
-  * **26 martie, ora 21:00** - adăugat **checker** 
  
 ===== Obiective ===== ===== Obiective =====
  
 În urma realizării acestei teme: În urma realizării acestei teme:
-  * veţi învăţa să lucraţi cu Dictionare ​ +  * veţi învăţa să lucraţi cu Arbori 
-  * vă veţi familiariza cu rezolvarea unei probleme reale prin structuri ​de date din ce in ce mai eficiente  ​+  * vă veţi familiariza cu o structură ​de date folosită în editoare de text reale
  
 ===== Introducere ===== ===== Introducere =====
  
-**Problema estimarii cardinalitatii** (a numararii elementelor distincte) estein esentagasirea numarului de elemente unice dintr-o colectie de elemente care se pot repeta. +Scopul acestei teme este implementarea unei structuri de date arborescente 
-<note important>​ +numită ​**Rope**. Aceasta permite efectuarea eficientă de operații ​(inserareconcatenare
-Pentru cerintele **I** si **II**, vom rezolva o problema si mai restrictiva:​ gasirea **numarului de aparitii** pentru fiecare elementPentru cerinta **III**, vom vedea ca acest lucru e mai greu realizabil cand vine vorba de volume mari de date si, de aceea, ne vom rezuma la **gasirea numarului de elemente distincte**.</​note>​+ștergere etc.) pe un șir de caractere foarte lung (problemă caracteristică editoarelor ​de text 
 +de orice tip).
  
 +Un Rope este un arbore binar cu //2 proprietăți//:​
  
-**Conceptual, ne referim la:**+1. **Frunzele** conțin //​stringuri//​ de lungime >= 1
  
-INPUT:+2. Fiecare nod are o greutate asociată:
  
-''​134, 2, 2, 2, 3''​+- pentru **frunze**acest număr este //lungimea stringului//​ pe care îl conține fiecare
  
-OUTPUT:+- pentru **restul nodurilor**,​ acest număr este //suma greutăților frunzelor din 
 +subarborele stâng//
  
-**Pentru cerintele I si II:**+Prin punerea cap la cap a stringurilor din frunze, de la stânga la dreapta, obținem 
 +stringul pe care îl reprezintă Rope-ul.
  
-''​1 - 1''​ 
  
-''​2 - 3''​+<note important>​Înainte să vă apucați de implementare,​ citiți și secțiunile de **Precizări** si **FAQ**.</​note>​
  
-''​3 - 1''​ 
  
-''​34 - 1''​+Va trebui să implementați următoarele operații:
  
-**Pentru cerinta III:**+==== 1. Concat - 10p ====
  
-''​Exista 4 elemente distincte''​+<code c> 
 +RopeTree* concat(RopeTree* rt1, RopeTree* rt2); 
 +</​code>​
  
-<note warning>​**Fiecare** dintre cele **3** cerinte se va implementa intr-un **fisier separat**.</​note>​+Concat(R1, R2) concatenarea a 2 rope-uri
  
-===== I. Vector de frecventa ​25p =====+ex: Concat(Rope("​ab"​),​ Rope("​cde"​)) ​-> Rope("​abcde"​)
  
-La intrare se dau numere intre **0** si **2000000**. Gasiti numarul de aparitii ale fiecarui element, utilizand un **vector de frecventa**.+{{:​sd-ca:​teme:​tema3_2021:​concat.png?800 |}}
  
-Un vector de frecventa este un vector care are pe pozitia **i** //numarul de aparitii// ale elementului **i**. 
  
-{{:​sd-ca:​teme:​freq.png|}}+==== 2Index - 10p ====
  
-//Se garanteaza ca numarul de aparitii ale oricarui element este mai mic decat **256**.//+<code c> 
 +char indexRope(RopeTreert, int idx); 
 +</code>
  
-===== II. Hashtable cu open addressing - 25p ===== 
  
-La intrare se dau siruri ​de caractere. Gasiti numarul de aparitii ale fiecarui sir folosind ​un Hashtable cu politica de rezolvare a conflictelor de tip **open addressing** prin **linear probing**.+Index(R1, i) - obținerea caracterului ​de pe poziția i dintr-un rope
  
-Aceasta politica presupune cain momentul in care bucketul unde trebuie realizata insertia este deja ocupat, //se va cauta secvential o pozitie libera incepand cu bucketul urmator//.+ex: Index(Rope("​abcde"​)1) -> '​b'​
  
-{{:​sd-ca:​teme:​hash1.png|}}+{{:​sd-ca:​teme:​tema3_2021:​index.png?300 |}}
  
-Evident, daca si aceasta pozitie este ocupata, vom cauta prima pozitie libera in continuare.+==== 3Search - 20p ====
  
-{{:​sd-ca:​teme:​hash2.png|}}+<code c> 
 +char* search(RopeTree* rt, int start, int end); 
 +</​code>​
  
-Daca se va ajunge la finalul listei de bucketurise va continua de la inceput.+Search(R1start, end) - obținerea stringului din intervalul [start,end)
  
-{{:sd-ca:​teme:​hash3.png|}}+exSearch(Rope("​abcde"​),​ 1, 3) -> "​bc"​
  
-//Se garanteaza existenta a cel putin unui bucket liber in momentul fiecarei operatii de insertie.// **Pentru a satisface aceasta conditie, o idee ar fi ca dimensiunea Hashtable-ului sa fie egala cu numarul de siruri existente in fisierul de intrare.**+{{:sd-ca:​teme:​tema3_2021:​search.png?300 |}}
  
-Evident, daca in momentul unei operatii de selectie nu gasim cheia in bucketul in care ne-am astepta, vom continua cautarea secvential, aplicand un procedeu similar cu cel din momentul insertiei.+==== 4Split - 20p ====
  
-//Se garanteaza ca lungimea maxima a oricarui sir este maxim **100** de caractere.//+<code c> 
 +SplitPair split(RopeTreert, int idx); 
 +</code>
  
-//Se garanteaza ca numarul de aparitii ale oricarui element este mai mic decat **256**.//+Split(R1, i) - împărțirea unui rope în 2 rope-uri separate
  
-===== III. Estimatori probabilistici ​30p =====+ex: Split(Rope("​abcde"​),​ 2) -> (Rope("​ab"​),​ Rope("​cde"​))
  
-In cerintele anterioare am observat ca putem calcula cu exactitate numarul de elemente distincte (si numarul lor de aparitii) retinand, intr-un fel sau altul, fiecare element unic (ca pozitie intr-un vector, respectiv ca cheie intr-un hashtable). Din pacate, in aplicatiile din lumea reala, //aceasta strategie nu este sustenabila//​.+Exemplu 1:
  
-Sa ne imaginam urmatoarea situatie**Youtube** afiseaza pentru fiecare videoclip **numarul de vizualizari**. Pentru ca acest sistem sa nu fie abuzat (spre exemplu de un bot care vizioneaza acelasi videoclip incontinuu pentru a-i spori view count-ul), trebuie sa tina cont si de **numarul de utilizatori unici** care au accesat clipul.+{{:sd-ca:​teme:​tema3_2021:​split1.png?700 |}}
  
-Daca ar retine un id pentru fiecare utilizator, ar avea nevoie de o structura de date cu milioane de intrari, si asta doar pentru un singur clip. Tinand cont ca exista //peste **31 de milioane** de canale// (iar multe dintre ele au **peste 100 de clipuri**), acest lucru iese din discutie.+\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +Exemplu 2:
  
-Cum putem totusi sa ne indeplinim obiectivul?+{{:​sd-ca:​teme:​tema3_2021:​split2.png?700 |}} 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\ 
 +\\
  
-Problema la abordarea anterioara este faptul ca foloseste o cantitate de memorie proportionala cu numarul de utilizatori distincti **O(n)**. In cautarea unei solutii mai bune, va trebui sa obtinem o complexitate a spatiului mai mica (**O(sqrt(n))**,​ **O(logn)**,​ **O(1)** etc.)+Exemplu 3:
  
-Solutia gasita (ce urmeaza a fi implementata in tema) aduce cu sine un sacrificiu din punct de vedere al precizieidin moment ce nu stim exact ce utilizatori am avut, numarul total de utilizatori unici **nu va mai fi unul precis, ci doar aproximativ**.+{{:sd-ca:​teme:​tema3_2021:​split3.png?900 |}}
  
-In practica, acest lucru nu este un dezavantaj prea mare (intrucat rareori e relevanta diferenta dintre 1m vizualizari si 1.1m vizualizari).+==== 5Insert - 5p ====
  
-In ilustrarea functionarii algoritmului **HyperLogLog**, vom incepe de la o serie de principii simple pe care le vom pune cap la capajungand la descrierea algoritmului final.+<code c> 
 +RopeTreeinsert(RopeTreertint idxconst char* string); 
 +</​code>​
  
-<note important>​Sectiunile 1 si 2 sunt prezentate pentru a intelege de ce functioneaza HyperLogLog. Pentru a rezolva tema, trebuie sa implementati **doar algoritmul final (descris in sectiunea 3)**.</​note>​+Insert(R1, i, str- inserarea unui string pe o poziție a unui Rope
  
-==== 1. Probabilistic counting ====+ex: Insert(Rope("​abcde"​), ​1), "​123"​) -> Rope("​a123bcde"​)
  
-Sa presupunem ca generam un numar la intamplare. 
  
-Probabilitatea ca numarul sa inceapa cu **un** bit **0** este **1/2** (deoarece poate incepe fie cu 0fie cu 1).+<note important>​Dacă ați implementat corect operațiile Split și Concat, Insert-ul va fi practic ​un Spliturmat de două Concat-uri.</​note>​
  
-Probabilitatea ca numarul sa inceapa cu **2** biti **0** este **1/4** (deoarece poate incepe cu 00, 01, 10 sau 11). 
  
-Similar, probabilitatea ca numarul sa inceapa cu **3** biti **0** este **1/8**. Astfel, pentru a intalni un numar care sa inceapa cu **3** biti **0**, //va trebui sa generam, in medie, **8** numere//. 
  
-{{:​sd-ca:​teme:​probcount1.png|}}+==== 6Delete - 5p ====
  
-Privind aceasta observatie in sens invers, daca am generat numere aleatoare si secventa cea mai lunga de **0** de la inceputul oricarui numar a fost de lungime **3**, atunci avem doua posibilitati:​+<code c> 
 +RopeTreedelete(RopeTreert, int startint len); 
 +</​code>​
  
-am generat //cel putin **8** numere//+Delete(R1, start, len) ștergerea substringului din intervalul [start, start+len)
  
-am avut noroc si a trebuit sa generam //mai putin de 8 numere//+ex: Delete(Rope("​abcde"​),​ 1, 2) -> Rope("​ade"​)
  
-Evidentpentru valori mici precum **2** sau **3** biti consecutiviexista o sansa semnificativa sa generam numarul mai rapid (chiar din prima incercare), dar cu cat valorile devin mai mari, cu atat scade aceasta sansa.+<note important>​Dacă ați implementat corect operațiile Split și ConcatDelete-ul va fi practic două Split-uriurmate de un Concat.</​note>​
  
-In principiu, daca am primit valori aleatoare la intrare, si numarul maxim de biti **0** consecutivi initiali ai oricarui numar este **x**, putem spune ca am primit //intre **2^x** si **2^(x+1)** valori//.+=== Precizări === 
 +<note warning>​Aveți grijă ca Rope-urile create ​de funcțiile voastre să **respecte cele două proprietăți din definiția unui Rope**!</note>
  
-In viata reala, valorile primite la intrare //nu vor fi neaparat valori aleatoare//​. Mai mult, nu toate valorile ​de intrare vor fi numere intregi. Din acest motiv, vom trece aceste valori printr-o **functie de hash**. O functie de hash buna ar trebui sa ofere la iesire //valori uniform distribuite//​.+<note warning>​Nu încercați să vă folosiți ​de "​trucuri"​ pentru a ocoli dificultatea cerințelor.
  
-==== 2. LogLog ====+Următorele sunt exemple 
 +de operații care, deși returnează Rope-uri care respectă definiția, **nu vor fi punctate**:​ 
 +</​note>​
  
-Daca vrem sa imbunatatim performanta algoritmului nostru va trebui sa:+{{:sd-ca:​teme:​tema3_2021:​nu2.png?​400 |}}
  
-Atenuam efectul negativ al generarii rapide unui numar cu multi biti de **0** initiali+{{:sd-ca:​teme:​tema3_2021:​nu1.png?​600 |}}
  
-Oferim estimari //mai granulare// decat **puterile lui 2**+{{:sd-ca:​teme:​tema3_2021:​nu3.png?​600 |}}
  
-{{:​sd-ca:​teme:​2n_table.png|}}+<note warning>​Dacă nu sunteți siguri, puteți întreba pe forum. 
 +</​note>​
  
-O idee de rezolvare a primei probleme este sa impartim numerele in mai multe **bucketuri**. Cea mai usoara modalitate de a face acest lucru este sa impartim fiecare numar in 2 parti: //prima parte// va fi folosita pentru a determina bucketul, iar //a doua// va fi folosita ca pana acum. +=== Schelet + Checker ​=== 
- +{{:​sd-ca:​teme:​tema3_2021:checker.zip|DOWNLOAD}}
-{{:​sd-ca:​teme:​buckets.png|}} +
- +
-Singura diferenta fata de metoda precedenta este ca acum vom face maximul de zerouri consecutive //pentru fiecare bucket// si nu pentru toate numerele. +
- +
-Pentru a agrega aceste maxime vom folosi **media geometrica**. +
- +
-==== 3. HyperLogLog ==== +
- +
-Recapituland ce am prezentat in sectiunile precedente, in cadrul algoritmului HyperLogLog avem 3 etape: +
- +
-**1)** stabilim numarul total de bucketuri **m**, apoi initializam cu 0 un vector **M** de dimensiune **m**. +
-<note important>​Alegerea lui m este diferita de la caz la caz. In contextul problemei curente, puteti sa folositi valoarea m = $2^{11}$, insa exista si alte variante posibile.</​note>​ +
- +
-**2)** pentru fiecare numar citit de la intrare: +
- +
-- ii calculam hash-ul cu o functie de hash pentru numere intregi +
- +
-- pe baza primilor $\log_2(m)$ biti din hash determinam bucketul in care se afla (din cele $m$ bucketuri posibile); notam numarul bucketului cu **j** +
- +
-- calculam numarul de biti 0 initiali din restul hash-ului; notam acest numar cu **x** +
- +
-- M[j] = max(M[j], x) +
- +
-**3)** agregam valorile din toate bucketurile +
- +
- +
-In sectiunea precedenta, am mentionat ca pentru a agrega valorile din fiecare bucket, folosim media geometrica. Pentru a implementa HyperLogLog,​ vom folosi in locul ei urmatorea medie: +
- +
-{{:​sd-ca:​teme:​media.png|}} +
- +
-Ca exemplu, pentru bucketul evidentiat cu verde, **j = 6**, iar **M[j] = 2** +
- +
-{{:sd-ca:​teme:​buckets.png|}} +
- +
- +
-Avand aceasta medie **Z**, raspunsul final **E** (numarul de elemente distincte intalnite) va fi dat de urmatorea formula: +
- +
-{{:​sd-ca:​teme:​estimare.png|}} +
- +
-Explicatie:​ +
- +
-**m**, ca si pana acum, este numarul total de bucketuri folosite +
- +
-**$\alpha_m$** reprezinta //factorul de atenuare//, calculat in functie de **m** dupa urmatoarea formula: +
- +
-{{:​sd-ca:​teme:​alfa.png|}} +
- +
- +
-//Pentru ultima cerinta, citirea se va face dintr-un fisier al carui nume este primit ca parametru.//​ +
- +
-=== Precizări === +
-<note warning>​Rezolvati cerintele I si II utilizand structura de date ceruta. Nerespectarea acestui lucru va aduce la anularea punctajului pentru cerinta respectiva.</​note>​ +
- +
-<note important>​Având în vedere ca a 3-a parte a temei presupune implementarea unei structuri de date probabilistice,​ checkerul ofera punctajul daca raspunsul vostru se incadreaza intr-o marja de eroare de 10% fata de raspunsul corect.</​note>​ +
- +
-=== Checker === +
-{{:​sd-ca:​teme:​2-distinct-count.zip|CHECKER}} +
  
 Temele vor fi trimise pe [[https://​elf.cs.pub.ro/​vmchecker/​ui/#​SD|vmchecker]]. ​ Temele vor fi trimise pe [[https://​elf.cs.pub.ro/​vmchecker/​ui/#​SD|vmchecker]]. ​
Line 214: Line 193:
 Arhiva trebuie să conțină: Arhiva trebuie să conțină:
   * surse   * surse
-  * fișierul Makefile **din arhiva cu checkerul** 
   * fișier **README** care să conțină detalii despre implementarea temei   * fișier **README** care să conțină detalii despre implementarea temei
  
Line 222: Line 200:
  
   - 80p teste   - 80p teste
-  - **Fiecare** test este verificat ​cu valgrind. Dacă un test are memory leaks, nu va fi punctat.+  - Testele pentru operațiile elementare sunt verificate ​cu valgrind. Dacă un test are memory leaks, nu va fi punctat.
   - 20p README + comentarii/​claritate cod (ATENȚIE! Fișierul README trebuie făcut explicit, cât să se înțeleagă ce ați făcut în sursă, dar fără comentarii inutile și detalii inutile).   - 20p README + comentarii/​claritate cod (ATENȚIE! Fișierul README trebuie făcut explicit, cât să se înțeleagă ce ați făcut în sursă, dar fără comentarii inutile și detalii inutile).
   - Se acordă 20% din punctajul obținut pe teste, ca bonus pentru coding style. De exemplu, pentru o temă care obține maxim pe teste, se pot obține 20p bonus dacă nu aveți erori de coding style. Pentru o temă ce trece 18 teste din 20, se pot obține 18p dacă nu aveți erori de coding style.   - Se acordă 20% din punctajul obținut pe teste, ca bonus pentru coding style. De exemplu, pentru o temă care obține maxim pe teste, se pot obține 20p bonus dacă nu aveți erori de coding style. Pentru o temă ce trece 18 teste din 20, se pot obține 18p dacă nu aveți erori de coding style.
Line 232: Line 210:
  
 ===FAQ=== ===FAQ===
- ​**Q:​** ​Ce functii de hashing trebuie sa folosesc in tema, la cerintele II si III? \\ \\ + ​**Q:​** ​Pot modifica structurile ​din schelet? \\ \\ 
- ​**A:​** Puteti folosi orice functii doriti. Un exemplu ar fi cele din laborator. + ​**A:​** ​Nu.
- +
- ​**Q:​** La cerinta II functia mea de hashing nu imi genereaza deloc coliziuni. E ok? \\ \\ +
- ​**A:​** ​E in regula, insa codul care trateaza posibilitatea coliziunilor **trebuie sa existe**. +
- +
- ​**Q:​** In enuntul cerintei III sunt mentionate functiile matematice log si pow, insa checkerul nu permite folosirea functiilor matematice. Cum rezolvam problema asta \\ \\ +
- ​**A:​** Pentru a-l calcula pe **m** care e de forma $2^k$, puteti folosi shiftarea pe biti, adica <​code>​int m = 1 << k;</​code>​ Din moment ce k se stabileste in prealabil, $\log_2{m} = k$. +
  
-===Link-uri utile===+ ​**Q:​** Îmi pot implementa alte funcții helper, pe lângă cele cerute? \\ \\ 
 + ​**A:​** Desigur, chiar vă încurajăm să o faceți.
  
-[[https://​en.wikipedia.org/​wiki/​Count-distinct_problem]]+ **Q:** Deci pentru Insert si Delete pot să dau copy-paste la codul din funcțiile Split si Concat?\\ \\ 
 + ​**A:​** Nu dați copy-paste, vă vom scădea din punctaj pentru cod duplicat. Pur și simplu chemați funcțiile deja implementate cu parametri corespunzători
  
-[[https://​www.omnicoreagency.com/​youtube-statistics]]+ **Q:** Rope-urile trebuie echilibrate de către noi?\\ \\ 
 + ​**A:​** Nu.
  
-[[https://en.wikipedia.org/​wiki/​HyperLogLog]]+ **Q:** Nu mă prind cum aș putea folosi funcția ''​getTotalWeight''​ din scheletDe ce ați inclus-o?\\ \\ 
 + ​**A:​** Sincer, am folosit-o în implementarea soluției și am uitat să o ștergemDacă nu vi se pare evident cum v-ați putea folosi de ea și mai mult vă încurcă, puteți să o ștergeți :)
  
-[[https://en.wikipedia.org/​wiki/​Linear_probing]]+ **Q:** Am căutat informații despre Rope pe internet și am citit că greutatea unei frunze ar trebui să fie egală cu lungimea stringului pe care îl conțineRespectăm enunțul și o lăsăm 0? 
 + ​**A:​** Vom considera ambele variante drept corecte.
  
-[[https://​stackoverflow.com/​questions/​12327004/​how-does-the-hyperloglog-algorithm-work]]+ **Q:** Aș vrea să folosesc un string auxiliar în care să îmi extrag tot șirul din Rope și să mă folosesc de el mai departe. E în regulă? 
 + ​**A:​** Nu este permisă extragerea stringului din rope urmată de prelucrarea acestui string.
  
-</​hidden>​ 
sd-ca/teme/tema3-2021.1620855667.txt.gz · Last modified: 2021/05/13 00:41 by gabriel_danut.matei
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0