| Autor zadání | Radoslav Sabol |
|---|---|
| Úprava | Radoslav Sabol |
| Odevzdávané soubory | src/* |
| Konec odevzdání části A | 2026-04-21 24:00 |
| Konec odevzdání části B | 2026-05-18 24:00 |
| Opravy v zadání | 2026-04-16 21:41 2 |
|
Kostru k úkolu nelze stáhnout
|
Predstavenie úlohy
V kontexte kompresie v informačnej teórii uvažujme o konečnej, neprázdnej množine $\Sigma$ (alebo inač povedané, abecede) a pravdepodobnostnom rozdelení $P: \Sigma \rightarrow [0,1]$. Vašou úlohou je skonštruovať prefixové kódovanie $C: \Sigma \rightarrow \{0,1\}^*$. Kódovanie musí byť také, aby očakávaná dĺžka kódu $L(C) = \sum_{s \in \Sigma} P(s) \cdot\bigl|C(s)\bigr|$ bola asymptoticky minimalizovaná v súlade so Shannonovou entropiou $H(P) = −\sum_{s\in\Sigma} P(s) \log_2 P(s)$, pričom je rešpektovaná štandardizovaná forma kladená kanonickou verziou Huffmanového algoritmu.
Skvelé, nie? A teraz menej formálne. Vašou úlohou je implementovať Huffmanovo kódovanie, teda algoritmus pre bezstrátovú kompresiu dát, ktorý prevedie znaky vstupného súboru (odteraz "symboly") na postupnosti bitov (odteraz "kódové slová") také, že vo výsledku minimalizujete veľkosť výstupného súboru. Intuitívne – symbolom, ktoré sa vo vstupnom súbore vyskytujú najčastejšie, priradíte kratšie kódové slová, zatiaľ čo najviac zriedkavým symbolom priradíte tie najdlhšie.
Huffmanov kód má v praxi široké využitie ako súčasť moderných kompresných algoritmov. Konkrétne je kódovanie súčasťou komplexnejších algoritmov (DEFLATE) a má využitie v bežne používaných formátoch ako ZIP, gzip, a JPEG.
Prvá časť (A)
Vašou úlohou bude implementovať dve funkcie:
int huffman_encode(const char *input_path, const char *output_path);
int huffman_decode(const char *input_path, const char *output_path);
Parameter input_path obsahuje cestu ku vstupnému binárnemu súboru.
V prípade, že je cesta nullptr, alebo operačný systém nedokáže otvoriť súbor z ľubovoľného dôvodu, vypíšte na štandardný chybový výstup vhodnú hlášku a program ukončite.
Podobné pravidlá platia pre cestu ku výstupnému súboru output_path s jednou výnimkou.
V prípade, že je output_path nullptr, vypíšte výstup na stdout.
Obe funkcie vrátia 0 v prípade úspechu a ľubovoľné iné číslo v prípade chyby.
Program tak isto skončí s chybou v prípade, že je vstupný súbor prázdny.
Algoritmus pre huffman_encode
Uvažujme, že symbol má dĺžku 1 bajt.
-
Pre každý znak vo vstupnom súbore spočítame jeho absolútnu frekvenciu, t.j. jeho počet výskytov.
-
Vypočítajte optimálnu dĺžku kódu pre každý symbol vyskytujúci sa v súbore. Podľa algoritmu je potrebné vybudovať binárny strom odspodu nahor, kde listy obsahujú samotné symboly a ich frekvencie. Ku konštrukcii sa vám hodí vaša obľúbená implementácia prioritnej fronty, a mala by prebiehať nasledovne:
-
Do prioritnej fronty uložte všetky listy stromu (teda symboly a ich frekvencie)
-
Keď má prioritná fronta len jeden prvok, tak váš strom je hotový a onen prvok sa stáva koreňom stromu
-
Vyberte 2 najmenšie prvky z prioritnej fronty. Vytvorte nový uzol – jeho potomkovia budú práve vybrané prvky a jeho frekvencia bude súčet frekvencií potomkov. Vložte nový uzol do prioritnej fronty.
-
Vráťte sa na krok b.
-
Strom máte hotový. Dĺžka optimálneho kódovania pre daný symbol v bitoch je dĺžka cesty od koreňa stromu ku listu dotyčného symbolu. V momente, ako si tieto dĺžky uchováte, strom už nebudete naďalej potrebovať.
-
-
-
Priraďte symbolom konkrétne kódové slová podľa kanonického Huffmanovho kódovania:
-
Zoradte symboly vzostupne: primárne podľa dĺžky kódu, sekundárne podľa hodnoty symbolu (0–255)
-
Prvému symbolu v zoradenom zozname priraďte kód = 0
-
Pre každý ďalší symbol v zozname:
-
Ak má rovnakú dĺžku ako predchádzajúci symbol: kód = predchádzajúci_kód + 1
-
Ak je dlhší o k bitov: kód = (predchádzajúci_kód + 1) << k
-
-
-
Huffmanov kód je hotový, môžete ho použiť na zakódovanie vstupného súboru.
| Variant, ktorý v tejto úlohe implementujeme, je takzvaný "Kanonický Huffmanov kód". Od klasického algoritmu sa líši predovšetkým v kroku 3, ktorý nám zabezpečuje deterministickejšie správanie v prípadoch, že niektoré symboly majú identické frekvencie. |
| Vstupný súbor môže byť ľubovoľnej dĺžky – v žiadnom prípade ho nenačítajte do pamäte celý, aj keď to pre vás bude znamenať viacero priechodov |
Požiadavky na výstup
V tejto sekcii popíšeme, ako by mal vyzerať obsah výstupného súboru pre huffman_encode.
Bohužiaľ nestačí zapísať len zakódovaný bitový prúd – navyše potrebujeme aj určitú nápovedu, aby bol súbor deterministicky dekódovateľný.
Preto bude výstupný súbor rozdelený na 3 časti:
+------------------+------------------+------------------+ | number_of_codes | code_lengths | encoded_input | | (8 bytes) | (256 bytes) | (variable) | | uint64_t LE | 256× uint8_t | bitový prúd | +------------------+------------------+------------------+
-
number_of_codes– hodnota typuuint64_tv little-endian formáte, obsahuje počet zakódovaných symbolov. Tento parameter vám pri dekódovaní pomôže rozoznať rozdiel medzi validnými kódovými slovami a zarovnaním v poslednom bajte. -
code_lengths– pole 256 hodnôt typuuint8_t, kdecode_lengths[i]obsahuje dĺžku kódu pre symbol s hodnotou i (0–255). V prípade, že sa znak vo vstupe nevyskytuje, ponechajte túto hodnotu na nule. -
encoded_input– sekvencia bitov zodpovedajúca kanonickému Huffmanovmu kódovaniu. V prípade, že posledný bajt nie je dokonale zarovnaný, doplníme ho nulami.
Príklad
Máme vstupný súbor s nasledujúcim obsahom: "eebbeecdebeeebecceeeddeb bbeceedebeeddeeeeccee eedeeedeeebeedeceedeb eeedeceeedebe"
Výsledné kódovanie máte v tabuľke nižšie. Pri výpočte kódov sa používa kanonické zoradenie: najprv podľa dĺžky kódu, potom podľa hodnoty symbolu.
| Symbol | Frekvencia | Dĺžka kódu | Kódovanie |
|---|---|---|---|
'e' |
48 |
1 |
0 |
'd' |
12 |
2 |
10 |
'b' |
11 |
3 |
110 |
medzera |
3 |
4 |
1110 |
'c' |
8 |
4 |
1111 |
Pre lepšie pochopenie kroku 3 (priradenie kódov) uvádzame detailný výpočet:
| Symbol | Dĺžka | Výpočet kódu | Kód (binárne) |
|---|---|---|---|
'e' |
1 |
0 |
0 |
'd' |
2 |
(0 + 1) << 1 = 2 |
10 |
'b' |
3 |
(2 + 1) << 1 = 6 |
110 |
medzera |
4 |
(6 + 1) << 1 = 14 |
1110 |
'c' |
4 |
14 + 1 = 15 |
1111 |
Dĺžky kódovaní boli odvodené zo stromu, ktorý nájdete nižšie:
(82)
/ \
(34) e(48)
/ \
d(12) (22)
/ \
(11) b(11)
/ \
space(3) c(8)
Po zakódovaní bude výstupný súbor v hexadecimálnom zápise vyzerať následovne:
00000000: 5200 0000 0000 0000 0000 0000 0000 0000 R............... 00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000020: 0000 0000 0000 0000 0400 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000060: 0000 0000 0000 0000 0000 0304 0201 0000 ................ 00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000100: 0000 0000 0000 0000 363e 619f e29b b679 ........6>a....y 00000110: 3141 fe71 0862 7937 09e2 60 1A.q.by7..`
-
5200 0000 0000 0000(little-endian) jenumber_of_codes, ktorý odpovedá číslu 82 -
Následuje dlhá séria núl, keďže valná väčšina znakov je nevyužitých. Každopádne, môžete vidieť, ako prvá
04odpovedá dĺžke kódovania pre medzeru (ASCII 32), a0304 0201sú dĺžky kódovania pre znaky 'c', 'b', 'd', 'e' -
od
363eďalej je samotný obsah súboru. V binárnej podobe pre lepšie porovnanie s tabulkou vyzerá obsah následovne:00000108: 00110110 00111110 01100001 10011111 11100010 10011011 6>a... 0000010e: 10110110 01111001 00110001 01000001 11111110 01110001 .y1A.q 00000114: 00001000 01100010 01111001 00110111 00001001 11100010 .by7.. 0000011a: 01100000
Algoritmus pre huffman_decode
-
Načítajte hlavičku súboru, ktorá obsahuje počet symbolov v kóde a dĺžky prefixových kódov pre každý symbol.
-
Keďže máte k dispozícii dĺžky kódov podobne ako pri zakódovaní, zopakujte 3. krok z algoritmu kódovania, aby ste získali konkrétne kódové slová.
-
Kódové slová použite pri dekódovaní súboru. Pre vyhľadávanie správneho symbolu použite vhodnú dátovú štruktúru.
Do výstupného súboru zapisujte už len samotný obsah dekódovaného súboru. Intuitívne, výstup pre encode a následný decode by mal byť totožný so vstupným súborom. Navyše, keď nájdete akýkoľvek problém s konzistenciou (počet symbolov v hlavičke nesúhlasí s počtom symbolov v obsahu, obsah obsahuje znaky, ktoré nie sú Huffmanovým kódom…), program okamžite ukončite s vhodnou chybovou hláškou.
Testovanie
test/ máte okrem základných testov dodanú malú vzorku súborov v test/test_files - členenie do podadresárov je následovné:-
input/- vstupné súbory -
output/- očakávané výstupy -
test_outputs/- adresár do ktorého sa zapisujú výstupy vášho riešenia - môžete ich použiť na ďalšie ladenie
Keďže testy majú cesty k súborom zadané na pevno, budete ich musieť spúšťať z adresára hw5, a to následovne:
./build/test/src-test
Poznámky
-
Dbajte na korektnú prácu so súbormi a dynamicky alokovanou pamäťou.
-
Ošetrite všetky chybové stavy a vypíšte zmysluplné chybové hlášky.
Opravy v zadání
Reformat the new block about testing
1fd3be3
2026-04-16 21:41
Roman Lacko
-179,2 +179,3 Po zakódovaní bude výstupný súbor v hexadecimálnom zápise vyzerať násle=== Algoritmus pre huffman_decode+.Dekódovanie vstupného súboru by malo prebiehať nasledovne:-190,2 +191,3 Navyše, keď nájdete akýkoľvek problém s konzistenciou (počet symbolov v h=== Testovanie+.V adresári `test/` máte okrem základných testov dodanú malú vzorku súborov v `test/test_files` - členenie do podadresárov je následovné:-194,2 +196,3 Navyše, keď nájdete akýkoľvek problém s konzistenciou (počet symbolov v h. `test_outputs/` - adresár do ktorého sa zapisujú výstupy vášho riešenia - môžete ich použiť na ďalšie ladenie+Keďže testy majú cesty k súborom zadané na pevno, budete ich musieť spúšťať z adresára `hw5`, a to následovne:
Clarify student testing
3b7f0b4
2026-04-16 16:49
Radoslav Sabol
-189,2 +189,10 Navyše, keď nájdete akýkoľvek problém s konzistenciou (počet symbolov v h+=== Testovanie+.V adresári `test/` máte okrem základných testov dodanú malú vzorku súborov v `test/test_files` - členenie do podadresárov je následovné:+. `input/` - vstupné súbory+. `output/` - očakávané výstupy+. `test_outputs/` - adresár do ktorého sa zapisujú výstupy vášho riešenia - môžete ich použiť na ďalšie ladenie+Keďže testy majú cesty k súborom zadané na pevno, budete ich musieť spúšťať z adresára `hw5`, a to následovne:++ ./build/test/src-test