Termen de predare: Luni, 25 Mai 2015, ora 23:55
Deadline hard: Joi, 28 Mai, ora 23:55
23 Mai Modificare deadline: 25 Mai.
24 Mai Update script testare: limita de timp a fost marita la 100 de secunde.
În urma realizării acestei teme, studentul va fi capabil:
Pentru a putea rezolva această temă, studentul trebuie să fie familiar cu următoarele noţiuni:
Sarcina voastră este să implementaţi un program care să comprime / decomprime un fişier arbitrar, aplicând algoritmul Huffman. Aici este descris, pe scurt, algoritmul şi aveţi şi un exemplu pentru compresia unui şir de caractere (fişier text). Algoritmul funcţionează pentru orice tip de fişier (nu neapărat text): se consideră fişierul ca un şir de octeţi.
Programul vostru va trebui să primească la intrare numele unui fişier şi o serie de opţiuni, în felul următor:
./comp -c <nume_fisier_in>
sau
./comp -d <nume_fisier_in>.cmp
unde:
<nume_fisier_in>.cmp
decompressed_<nume_fisier_in>
Astfel, daca numele fişierului este poza.bmp
, fişierul comprimat se va numi poza.bmp.cmp
, iar fişierul decomprimat decompressed_poza.bmp
Se va considera şirul de octeti din fişier ca pe un şir de simboluri. Acestui şir de simboluri va trebui să-i aplicaţi o codificare Huffman, (a se vedea documentaţia pusă la dispoziţie) asociind fiecărui simbol o probabilitate de apariţie egală cu frecvenţa simbolului în fişier (deci va trebui să parcurgeţi la început întregul fişier pentru a determina aceste probabilităţi). Pentru reprezentarea cozii prioritare din care veţi extrage nodurile cu costul cel mai mic, veţi folosi o structură de heap. Pentru heap, veţi avea nevoie de o funcţie care compară două noduri între ele, şi veţi folosi următoarele reguli (presupunem ca prioritatea este maximă în vârful cozii):
Practic, aceste reguli specifică ordinea în care se vor scoate elementele din coadă: se preferă cele cu probabilitate mică, iar dacă sunt mai multe cu aceeaşi probabilitate, le vom lua pe cele de pe nivelurile cele mai mici, pentru a păstra echilibrat arborele. Daca si inaltimile sunt egale, luam in considerare reprezentantul. Astfel vom avem un mod unic de a scoate elementele din coada prioritara.
Reprezentantul unui nod se defineste in felul urmator:
Arborele Huffman construit va fi necesar şi in pasul de decompresie, astfel că este nevoie sa fie salvat in fişier. Reprezentarea arborelui în fişier trebuie să fie cel de vector de structuri de forma:
struct HuffmanSerializedNode { uint8_t isTerminal; union { uint8_t value; struct { uint16_t leftChild; uint16_t rightChild; } __attribute__((__packed__)) childData; } __attribute__((__packed__)); } __attribute__((__packed__));
Mai întâi se scrie în fişier un întreg de tip uint16_t care specifică numărul de elemente de tip HuffmanSerializedNode care urmează, apoi urmează vectorul de structuri, unde câmpurile din fiecare structură semnifică:
isTerminal
specifică dacă nodul din arbore este terminal (valoare 1) sau nu (valoare 0); dacă nodul este terminal, va fi valid câmpul value
din union;value
reprezintă valorea simbolului (valoarea octetului) din frunza arborelui;childData
reprezintă informaţii despre descendenţi, în cazul nodurilor neterminale; leftChild şi rightChild reprezintă indecşi în vectorul de structuri (indexarea porneşte de la 0), şi indică structurile ce codifică descendenţii stâng, respectiv drept, ai nodului. Trebuie remarcat faptul că într-un arbore Huffman, un nod fie este terminal, fie are exact doi descendenţi, deci cele două câmpuri vor fi valide simultan. De asemenea, structura de pe poziţia 0 din vector va fi rădăcina arborelui Huffman.
După ce este scris arborele Huffman se scrie şi dimensiunea fişierului de intrare ca un element de tip întreg fara semn(necesar la decomprimare pentru a şti câte simboluri trebuie decodificate). Urmează simbolurile scrise codificat în ordinea în care au fost citite din fişierul original. În urma aplicării arborelui Huffman asupra simbolurilor, se obţine un şir de biţi.
Se convine ca un bit '1' să reprezinte o trecere spre descendentul drept în arborele Huffman construit, în timp ce un bit '0' să însemne o trecere spre descendentul stâng.
Aceştia vor fi scrişi in fişerul rezultat, grupaţi pe octeţi, în felul următor:
[7 6 5 4 3 2 1 0] [15 14 13 12 11 10 9 8] [23 22 21….
Conform exemplului de aici sirul “ana are mere” se codifica astfel:
Bit index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Value | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
Octetii care vor fi scrisi in fisier sunt
Atentie: in cazul in care numarul total de biti nu este multiplu de 8, se vor adauga biti de 0 ca padding.
Pentru operaţiile pe biţi folosiţi DOAR variabile unsigned.
Recomandarea noastră este să folosiţi tipurile de date uint8_t / uint16_t / uint32_t după caz.
Conform celor spuse mai sus, formatul unui fisier comprimat trebuie sa arate astfel (in aceasta ordine):
Camp | K | V | N | L |
---|---|---|---|---|
Semnificatie | numarul de noduri din arbore | arborele serializat | dimensiunea fisierului original | fisier comprimat |
Tip/Dimensiune | 1 * uint16_t | K * HuffmanSerializedNode | 1 * uint32_t | sir de biti ce reprezinta fisierul comprimat |
Testarea temei se va face folosind vmchecker.
Arhiva voastră trebuie să conţina sursele, un fişier README şi un fişier Makefile cu 2 reguli build şi clean. Regula build va compila sursele şi va genera un executabil numit comp. Regula clean va sterge fisierele temporare generate in timpul compilării (dacă există) şi executabilul comp.
Fişierul README trebuie să conţină detalii despre implementarea temei.
Puteţi descărca checker-ul pentru testare locală sub linux de aici.