Tema 3 - Biblioteca

Responsabili:

Deadline soft: 28 aprilie, ora 23:55

Depunctare întârziere după depășirea deadline-ului soft: -10p/zi

Deadline hard: 1 mai, ora 23:55

Changelist:

  • 09 aprilie, ora: 09:24 actualizare checker (in conformitate cu constrangerile cerintei)
  • 14 aprilie, ora: 19:45 actualizare checker (adaugare bonus)

Introducere

Fiind aproape de terminarea anului I de facultate, dornici sa ne afirmam si sa ne testam cunostintele acumulate pana acum, acceptam sa participam intr-un proiect de modernizare a unei biblioteci.

In prima faza a proiectului, cea de documentare si intelegere a cerintelor clientului, aflam de la directorul institutiei urmatoarele:

  1. se doreste o metoda de indexare a fiecarei carti intr-un format electronic, astfel incat operatiile de gestiune a volumelor sa fie simple, rapide, moderne, sigure etc. (operatia PUT)
  2. majoritatea cautarilor in baza de date a bibliotecii nu se realizeaza dupa numele cartilor ci dupa continut astfel incat este nevoie de o metoda rapida de cautare, tinand cont de volumul mare de carti. Practic, date niste cuvinte cheie (ce se afla in continutul cartilor) trebuie sa se gaseasca numele cartilor ce le contin. (operatia GET)
  3. multi clienti organizeaza competitii unu-la-unu pe baza cartilor citite astfel: primul alege numele cartii, iar oponentul sau scrie o lista de cuvinte continute in acea carte. In cazul in care nu a gresit niciun cuvant (toate cuvintele mentionate se gasesc in carte), acesta acumuleaza un punctaj egal cu numarul de cuvinte scrise, si 0p altfel. Dupa un numar prestabilit de runde, se desemneaza castigatorul: persoana cu cele mai multe puncte acumulate! Deoarece partea de verificare a cuvintelor este foarte costisitoare manual, se doreste implementarea in aplicatia electronica si a acestei functionalitati. Formal, dat un nume de carte si o lista de cuvinte, sa se determine daca toate cuvintele se afla in acea carte. (operatia PLAY)

Detalii implementare

Dându-se ca input o serie de comenzi, fiecare comanda specifica aplicatiei: PUT, GET sau PLAY, se doreste generarea unui output cu raspunsurile corespunzatoare. Atat fomatele comenzilor cat si a raspunsurilor sunt detaliate in continuare.

Input

Input-ul constă dintr-o insiruire de comenzi, fiecare pe cate o linie. Citirea se realizeaza pana la intalnirea caracterului de sfarsit de fisier EOF.

comanda1
comanda2
...

Tipurile de comenzi (detaliate ulterior):

  • PUT
  • GET
  • PLAY

Comanda PUT

Sintaxa comenzii PUT este:

      PUT titlu_carte  cuv1 cuv2 .. cuvN

Rolul acestei comenzi este de a indexa conținutul cărții titlu_carte si anume: cuv1 cuv2 .. cuvN, astfel încât la orice căutare ulterioara de cuvinte apartinand cartii, aceasta să fie printre rezultatele găsite.

Această comandă se poate reprezenta în două moduri:

  • Într-un hashtable, se rețin asocieri de tipul titlu_carte - lista de cuvinte. Astfel cheia intrării în hashtable este titlu_carte și valoarea: lista de cuvinte: cuv1 cuv2 .. cuvN. Există o serie de avantaje și dezavantaje pentru această abordare:
    • operația de PUT este mult ușurată, întrucât nu se fac deloc prelucrări asupra conținutului.
    • operația de GET în cadrul acestei structuri este mult îngreunată, întrucât se caută cuvinte și nu cărți (ar trebui ca pentru fiecare carte în parte, să căutăm dacă are în lista de cuvinte (valoarea cheii) cuvântul căutat).
    • (Remember: proprietatea de bază a unui hashtable este că operația de căutare se efectuează în O(1) complexitate)
  • Inverted index. Tot într-un hashtable, se rețin asocieri de tipul cuvânt - lista de cărți. Astfel cheia intrării în hashtable este cuvântul și valoarea lista de titluri de cărți ce conțin acel cuvânt. În acest fel, căutarea unui cuvânt este efectuată mult mai ușor, întrucât hashtable-ul este indexat după cuvinte.

În tema voastră, va trebui să implementați cea de-a doua tehnică de reținere a informațiilor, cea în care într-un hashtable se rețin asocieri de tipul cuvânt - listă de carti.

Astfel dacă se dau la input următoarele 3 comenzi:

PUT titlu_carte1 cand rasare soarele
PUT titlu_carte2 afara este soarele
PUT titlu_carte3 cand mergem afara

Hashtable-ul după aceste 3 comenzi va arăta în felul următor:

  • afara → {titlu_carte2, titlu_carte3}
  • cand → {titlu_carte1, titlu_carte3}
  • este → {titlu_carte2}
  • mergem → {titlu_carte3}
  • rasare → {titlu_carte1}
  • soarele → {titlu_carte1, titlu_carte2}

Observații comandă PUT:

  • Două comenzi PUT pentru același titlu de carte se consideră adăugări de conținut și nu ștergere sau înlocuire de conținut.
  • Un cuvânt poate apărea de mai multe ori în cadrul unei cărți, insa nu e nevoie sa retineti de cate ori apare.

Comanda GET

Sintaxa comenzii GET este:

    GET cuv1_interogare cuv2_interogare .. cuvN_interogare

Această comandă va întoarce toate cărțile în care apar toți termenii interogării. De exemplu, pentru următoarele interogări:

  • GET cand
    • Rezultatul va fi {titlu_carte1, titlu_carte3}
    • Cuvântul „cand” apare în cartea 1 si in cartea 3.
    • Cele două cărți sunt afișate în ordine alfabetică.
  • GET cand rasare
    • Rezultatul va fi {titlu_carte1, titlu_carte3} SI {titlu_carte1} = {titlu_carte1}
    • SI - intersectie de mulțimi
  • GET cenusareasa
    • Rezultatul va fi BOOK_NOT_FOUND (Nu există nicio carte indexată care să conțină cuvântul cenusareasa)

Observații comandă GET:

  • Numele cărților întoarse vor fi afișate în ordine alfabetică
  • Dacă nu există nicio carte care să conțină toți termenii interogării, atunci se va afișa BOOK_NOT_FOUND

Intrucât va trebui să afisați toate cărțile care conțin toți termenii interogării, va trebui să găsiți o metodă de intersecție rapidă (eficienta).

Comanda PLAY

Sintaxa comenzii PLAY este:

   PLAY titlu_carte cuv1_concurent cuv2_concurent .. cuvN_concurent
   

Această comandă verifică dacă toți termenii din interogarea: cuv1_concurent cuv2_concurent .. cuvN_concurent există în cartea titlu_carte. Dacă toți termenii există în carte, atunci se va afișa YOU_WIN, iar dacă nu, se va afișa YOU_LOSE.

Exemplu: INPUT:

PLAY titlu_carte1 soarele cand
PLAY titlu_carte3 mergem bal

OUTPUT:

YOU_WIN
YOU_LOSE

Observații generale INPUT

  • Delimitatorul între cuvintele din conținutul cărților sau din interogare este doar caracterul spațiu.
  • Aveti la dispozitie un schelet de cod care să realizeze parsarea unei comenzi intr-un vector de string-uri.
  • Fiecare comandă se află pe o linie.
  • Nu există un număr maxim de comenzi și nici nu există un număr maxim de caractere pe o linie.
  • Se garantează ca numele cartilor sau al comenzilor de interogare nu va contine spatii.
  • Comenzile se termina cu caracterul terminator de linie(\n), care se garantează ca nu apare în conținutul cărților sau în interogări.
  • Output-ul comenzilor GET și PLAY este influențat doar de comenzile PUT de până atunci. Așadar nu va trebui să rețineți comenzile date, ci doar să le procesați imediat.
  • Output-ul comenzilor GET și PLAY trebuie scris la stdout, în ordinea citirii comenzilor GET și PLAY din input.
  • Comanda PUT nu are output. Ca urmare, in urma unei comenzi PUT voi trebuie doar sa actualizati corespunzator structura de date, fara sa afisati nimic.

Observații generale timpi

  • Fiecare test are asociat un timeout, calculat in functie de dimensiunea lui. Este interzisa folosirea oricaror optimizari de compilator (exemplu: -O2, -O3, etc.) pentru incadrarea in timpul limita.
  • Acesti timpi sunt specifici rularii temei pe vmchecker si nu rularii temei local, unde este posibil sa obtineti timpi diferiti (mai buni). Timpul de rulare/test considerat va fi numai cel de pe vmchecker.

Hashtable

Pentru implementarea eventualelor structuri de date de tip hashtable din temă, puteți pleca de la implementarea din Laboratorul 6, bazată pe vector de liste înlănțuite. Evident, elementele din dictionar sunt de tipul (cheie, valoare). In cazul temei, valoare va fi reprezentat sub forma unui vector std::vector<std::string> in care veti mentine lista cartilor in care se gaseste cuvantul din cheie. Pentru a optimiza cautarea in acest vector va trebui sa mentineti acest vector sortat lexicografic si sa optimizati cautarea in vector folosind un algoritm specific acestei proprietati (de sortare).

Pentru a putea efectua tema, trebuie să țineți cont de următoarele observații:

  • Operația de GET dintr-un hashtable trebuie să poată fi efectuată în timp constant și să nu fie influențată de numărul de intrări în hashtable. Din acest motiv, dacă implementați hashtable-ul ca un vector, așa cum este în laboratorul de hash-uri, trebuie să tineți cont de raportul între câte intrări sunt în hashtable și capacitatea lui.
    • Dacă acest raport, numit factor de încărcare, depășeste o valoare predefinită (se recomandă ca această valoare să fie 0.7), atunci va trebui să redimensionați vectorul și bineînțeles va trebui să introduceți vechile intrări în noul vector.
    • Pentru vectorul inițial din hashtable, puteți considera o capacitate predefinită de intrări, de exemplu 16, urmând ca la fiecare redimensionare să dublați capacitatea vectorului.
    • Va trebui să gasiți o metodă prin care două liste de cărți se intersectează foarte rapid.
    • Întrucât se lucrează doar cu string-uri puteți folosi funcția de hash din laboratorul 6.

In cadrul temei este obligatorie setarea dimensiunii initiale a dictionarului la valoarea 16 si setarea factorului de incarcare la valoarea 0.7, pentru ca toate implementarile voastre sa faca acelasi numar de redimensionari.

Următorul exemplu vă va ajuta să înțelegeți cum se redimensionează un hashtable:

Înainte:

După redimensionare:

Explicații:

  • Cele două poze indică același hashtable, odată înainte de redimensionare, având capacitatea de 10 și apoi după redimensionare, având capacitatea de 20.
  • În cele două poze sunt arătate doar pozițiile din vector care conțin o listă nevidă de elemente.
  • În poze, numele țărilor indică cheia din hashtable, iar valorile (listele de carti care conțin aceste cuvinte) nu au fost afișate
  • Între paranteze, pentru fiecare țară este arătat valoarea hash-ului calculat cu funcția din laborator (hash(“romania”)==1443285676)
  • Poziția din vector în care s-a inserat o cheie este indicele hash(cuvânt)%capacitate
  • În prima situație, în hashtable sunt deja 7 elemente (70% din capacitatea hashtable-ului este ocupată) și se inserează o nouă cheie (“romania”).
  • În acest caz, întrucât factorul de încărcare depășeste 70%, trebuie ca hashtable-ul să fie redimensionat, iar cheile remapate la noii indecși.
  • Astfel cheia “america” a fost mutată în elementul 15 din vector, întrucât hash(“america”)%20==15 samd.
  • Cheia “romania” a fost inserată pe poziția 16.

Schelet de Cod

Aveti la dispoziție un schelet de cod, care realizează citirea și parsarea comenzilor. Acesta va scuteste de efortul de a implementa partea nefunctionla a temei. Scheletul de cod se gaseste aici: 3-biblioteca_schelet

Nu este obligatorie folosirea acestui schelet de cod.

Exemple

Input Output
PUT titlu_carte1 cand rasare soarele
PUT titlu_carte3 cand mergem
GET mergem titlu_carte3
PLAY titlu_carte1 cand rasare YOU_WIN
PLAY titlu_carte1 ana are YOU_LOSE
PLAY titlu_carte3 mergem YOU_WIN
PUT titlu_carte3 afara soarele
GET cand soarele titlu_carte1 titlu_carte3
PUT titlu_carte2 afara este soarele
GET afara titlu_carte2 titlu_carte3
GET afara titlu_carte2 titlu_carte3
GET cand mergem titlu_carte3
GET temaSD BOOK_NOT_FOUND

Așa cum observați, pentru claritate răspunsul comenzilor GET/PLAY a fost pus pe rândul corespunzător din tabel, lăsând câte o linie goală pentru fiecare comandă PUT, dar voi NU trebuie să lăsați nicio linie goală. Pentru mai multe detalii, consultați checkerul disponibil.

Așa cum am amintit și în secțiunea corespunzătoare comenzii GET, cărțile sunt afișate în ordine alfabetică.

Bonus

Bucurosi ca am finalizat cu succes cea de-a doua faza a proiectului, anume implementarea corecta a bibliotecii virtuale, dorim sa aflam cum functioneaza aplicatia, “in mediul real”. Contactandu-l pe director, acesta ne spune ca lucrurile stau mult mai bine acum: gestiunea cartilor este mult usurata, clientii s-au obisnuit cu folosirea aplicatiei si a crescut numarul de competitii datorita simplificarii verificarilor. Insa, exista si o mica problema recenta, neprevazuta la inceputul proiectului: din cauza numarului mare de carti, si a numarului mare de interogari: GET/PLAY, aplicatia este destul de lenta. Deoarece momentan nu exista fonduri suficiente pentru achizitionarea unor echipamente hardware dedicate si nici de upgrade-uri la cele existente, acesta ne propune o prima pentru optimizarea (software) a aplicatiei.

Cerinta bonus

Optimizati aplicatia din punct de vedere a timpului de rulare, pastrand implemetarea si algoritmul folosit in tema, astfel incat sa respecte timeout-urile noi (setate in checker, la sectiunea bonus). Pentru indeplinirea cerintei, este obligatoriu sa pastrati algoritmul si structura descrisa in cerinta temei. Scopul bonusului este scrierea corecta si eficienta a codului. Cateva sfaturi in acest sens ar fi:

  • atentie la copieri inutile:
    • atunci cand trasmiteti parametrii: transmiteti parametrii prin referinta acolo unde este posibil
    • copieri de structuri de date. Atentie cand lucrati cu structuri de date in C++. Spre deosebire de C, unde o copiere de structura rezulta, in general, in copieri de adrese (de poiteri), in C++ marea majoritate a structurilor implementeaza operatorul de copiere pentru o copiere totala a structurii. Exemplu:
std::vector<T> a, b;
//fill a with many elements
b = a; //realizeaza o copiere (duplicare a tuturor elementelor din a)
  • eliminati flag-ul '-g' din Makefile. Acest flag realizeaza imbogatirea executabilului cu informatii de debugging, foarte utile in acest sens, dar care aduc un impact negativ asupra performantei.
  • in general, lucrati ingrijit: modularizati codul, considerati mebrii claselor private si folositi metode pentru interactiunea cu clasele, organizati clasele pe diverse nivele logice/de abstractizare. Intr-adevar, asta nu va creste performata, dar va va face sa vedeti mai usor eventuale bug-uri in implementare si actiuni care se puteau realiza mai eficient.
  • pentru clasa vector: in momentul in care folosim structura vector implementata in C++, este bine sa folositi functia reserve(size_type n), ce aloca in prealabil n elemente, atunci cand capacitatea finala (sau minima) a vectorului este cunoscuta. De obicei se foloseste impreuna cu functia push_back(T& el). Un exemplu de folosire este:
std::vector<int> intVect;
for (int i = 0; i < 10000; ++i)
    intVect.push_back(i);
// In acest caz, in vector se aloca un element nou, pentru fiecare apel push_back. 
// Apelul de alocare este unul costisitor.
// Chiar mai ineficient este cazul cand, din lipsa posibilitatii alocarii
// unui element nou in continuarea elementelor vectorului, se copiaza toate elementele
// intr-un spatiu continuu suficient de mare. Acest proces poate sa apara, in cel mai
// defavorabil caz, la fiecare alocare.

    
// Solutie:
std::vector<int> intVect;
intVect.reserve(10000);
for (int i = 0; i < 10000; ++i)
    intVect.push_back(i);
// In acest caz se va face o alocare de 10000 elemente o singura data (la inceput)
// dupa care nu va mai fi nevoie de nicio realocare.

Trimitere și punctare

Temele vor trebui trimise pe vmchecker. Atenție! Temele trebuie trimise în secțiunea Structuri de Date (CA).

Arhiva trebuie să conțină:

  • surse
  • fișier Makefile cu două reguli:
    • regula build: în urma căreia se generează un executabil numit tema3
    • regula clean care şterge executabilul
  • fisier README care să conțină detalii despre implementarea temei

Punctare:

  • 90 puncte obținute pe testele principale de pe vmchecker
    • Test1: Test SIMPLE GET
    • Test2: Test SIMPLE GET/PLAY
    • Test3: Test MEDIUM GET/PLAY
    • Test4: Test MEDIUM GET/PLAY
    • Test5: Test HARD GET/PLAY
    • Test6: Test HARD GET/PLAY
    • Test7: Test HARD GET/PLAY
    • Test8: Test VERY HARD GET/PLAY
    • Test9: Test Valgrind, verifică dacă eliberați toată memoria
  • 10 puncte: coding style și README
  • 10 puncte obținute pe testele bonus de pe vmchecker
    • 2p: Test5, cu un alt TIMEOUT
    • 2p: Test6, cu un alt TIMEOUT
    • 2p: Test7, cu un alt TIMEOUT
    • 2p: Test8, cu un alt TIMEOUT
    • 2p: Test9, cu un alt TIMEOUT
  • TOTAL: 110 puncte

Timpul de rulare/test: Există un timp de rulare specific pentru fiecare test, astfel încât să fie verificat că implementați bine operațiile hashtable si ca respectati optimizarile precizate in cerinta. Acesti timpi sunt specifici rularii temei pe vmchecker si nu rularii temei local, unde este posibil sa obtineti timpi diferiti (mai buni). Timpul de rulare/test considerat va fi numai cel de pe vmchecker.

Pentru alte detalii legate de punctare citiți Regulamentul General de Trimitere a Temelor.

Checker

Puteți descărca checker-ul, cu testele publice aferente, de aici: 3-biblioteca_checker_v2

Link-uri extra

* Un post scurt despre interted index @google + video

  • este mai complex decat inverted index, dar poate il veti gasi interesant

FAQ

Q: Se poate folosi STL?

R: Puteți folosi clasele std::vector, std::list și std::string pentru a reține cuvintele, titlurile cartilor etc. De asemenea, in general este recomandat sa folositi iteratorii nativi structurii, precum std::vector<T>::iterator in C++, acestia fiind mai eficienti in parcurgerea structurilor (iterabile). Vedeti exemple de utilizare in scheletul de cod.

sd-ca/2014/teme/teme-03.txt · Last modified: 2015/02/17 13:46 by alexandru.olteanu
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