\authorWithDegrees{Ladislav Zemek}%jméno autora včetně současných akademických titulů
\author{Ladislav Zemek}%jméno autora bez akademických titulů
\supervisor{Ing. Radomír Polách}
\acknowledgements{Rád bych poděkoval Ing. Radomírovi Poláchovi za odborné vedení, pomoc a vstřícnost při vedení bakalařské práce.}
\acknowledgements{Rád bych poděkoval Ing. Radomírovi Poláchovi za pomoc a vstřícnost při vedení bakalařské práce.}
\abstractCS{Tato bakalářská práce se~zabývá návrhem, analýzou, implementací a~testováním tří kompresních metod LZ77, LZ78 a~LZW do~knihovny SCT. Knihovna je koncipována tak, aby v~budoucnu nabídla uživatelům velkou škálu kompresních metod a~tak usnadnila výběr vhodné metody pro jejich projekt. Uvnitř této práce se zabývám vhodným návrhem a~popisem daných algoritmů. U~všech metod se mi podařilo při vhodně zvolených parametrech dosáhnout zmenšení testovaných korpusů vždy alespoň o~30~\%. Přínosem této práce je přispění do knihovny, která může v budoucnu usnadnit některým vývojářům práci.}
\abstractEN{This bachelor thesis deals with the design, analysis, implementation and~testing of~three compression methods LZ77, LZ78 and~LZW in~the~SCT library. The~library is designed to~offer users a~wide range of~compression methods in~the~future to~facilitate the~choice of~the~right method for their project. Inside this thesis I deal with suitable design and~description of~given algorithms. For all methods, I managed to~reduce the tested corpuses with~appropriately selected parameters at~least by~30~\%. The benefit of~this work is contributing to a~library that can make it easier for some developers to work in the~future.}
V~době, kdy se začali počítače rozšiřovat do~běžného života, vznikla potřeba ukládat čím dál větší objem dat. Z~dnešního pohledu to nevypadá jako problém, jednoduše si uložíte několika gigabytový soubor na svůj pevný disk s~kapacitou stovek gigabytů. Ovšem tehdy měli pevné disky jen desítky megabytů, což je téměř sto tisíckrát méně než dnes, paměti tedy rozhodně nebylo nazbyt. Proto bylo potřeba data nějakým způsobem zmenšit, za~předpokladu, že nedojde ke~ztrátě informací.
V~době, kdy se začali počítače rozšiřovat do~běžného života, vznikla potřeba ukládat čím dál větší objem dat. Z~dnešního pohledu to nevypadá jako problém, jednoduše si uložíte několika gigabytový soubor na svůj pevný disk, který má kapacitu stovky gigabytů. Dříve ovšem měli pevné disky kapacitu jen desítky megabytů, což je téměř sto tisíckrát méně než dnes. Paměti tedy rozhodně nebylo nazbyt. Proto bylo potřeba data nějakým způsobem zmenšit a tím šetřit místo na disku. Samozřejmě zapředpokladu, že nedojde ke~ztrátě informací.
Právě za tímto účelem vznikla bezztrátová komprese dat, která zakóduje data pomocí důmyslných algoritmů. Nová data pak mají výrazně menší velikost než ta původní.\cite{komprese} Jsou tedy vhodnější pro uložení na~disk nebo k~odeslání přes internet. Data je následně možné dekódovat pomocí dekomprese, což je inverzní operace ke kompresi. Výstupem dekomprese jsou původní data.
Jelikož v knihovně SCT nejsou naimplementovány zmíněné algoritmy, rozhodl jsem se je do knihovny doplnit.
\section{Pojmy}
V této kapitole bych rád vysvětlil a definoval některé pojmy.
\begin{itemize}
\item\textbf{Komprese/komprimace dat} -- Zmenšování objemu dat.
\item\textbf{Bezztrátová komprese dat} -- Komprese dat, při které nedochází ke ztrátě informace.
\item\textbf{Dekomprese/dekomprimace dat} -- Inverzní operace ke kompresi. Ze zmenšených dat obnovím ty původní.
\item\textbf{Triplet} -- V této bakalářské práci je tripletem myšlena jakákoliv n-tice. Je tomu tak proto, že v knihovně SCT je každá n-tice zpracovávána pomocí třídy TripletProcessor. Neznamená to tedy vždy trojici (LZ77). V závislosti na metodě může jít i o dvojici (LZ78) nebo jednici (LZW).
\item\textbf{Adaptivní kompresní metoda} -- Tyto metody si vytváří struktůry za běhu programu v závislosti na vstupních datech. Vytvořené struktůry pak používá ke komprimaci. Není potřeba tyto struktůry ukládat společně s komprimovanými daty.
\item\textbf{Slovníkové kompresní metody} -- Tyto metody používají pro komprimaci slovník, který v průběhu komprimace i dekomprimace roste přidáváním nových frází. Proto patří slovníkové metody mezi adaptivní kompresní metody. Komprimovaná data poté obsahují indexy frází ve vytvářeném slovníku.
\item\textbf{Symetrická kompresní metoda} -- Komprimace i dekomprimace těchto metod má stejnou složitost.
\item\textbf{Asymetrická kompresní metoda} -- Komprimace i dekomprimace těchto metod má odlišnou složitost.
\item\textbf{Entropie} -- míra neurčitosti systému
\end{itemize}
\end{introduction}
\chapter{Pojmy}
V této kapitole bych rád vysvětlil a definoval některé pojmy.
\begin{itemize}
\item\textbf{Komprese/komprimace dat} -- Zmenšování objemu dat.
\item\textbf{Bezztrátová komprese dat} -- Komprese dat, při které nedochází ke ztrátě informace.
\item\textbf{Dekomprese/dekomprimace dat} -- Inverzní operace ke kompresi. Ze zmenšených dat obnovím ty původní.
\item\textbf{Triplet} -- V této bakalářské práci je tripletem myšlena jakákoliv n-tice. Je tomu tak proto, že v knihovně SCT je každá n-tice zpracovávána pomocí třídy TripletProcessor. Neznamená to tedy vždy trojici (LZ77). V závislosti na metodě může jít i o dvojici (LZ78) nebo jednici (LZW).
\item\textbf{Adaptivní kompresní metoda} -- Tyto metody si vytváří struktůry za běhu programu v závislosti na vstupních datech. Vytvořené struktůry pak používá ke komprimaci. Není potřeba tyto struktůry ukládat společně s komprimovanými daty.
\item\textbf{Slovníkové kompresní metody} -- Tyto metody používají pro komprimaci slovník, který v průběhu komprimace i dekomprimace roste přidáváním nových frází. Proto patří slovníkové metody mezi adaptivní kompresní metody. Komprimovaná data poté obsahují indexy frází ve vytvářeném slovníku.
\item\textbf{Symetrická kompresní metoda} -- Komprimace i dekomprimace těchto metod má stejnou složitost.
\item\textbf{Asymetrická kompresní metoda} -- Komprimace i dekomprimace těchto metod má odlišnou složitost.
\item\textbf{Entropie} -- míra neurčitosti systému
\end{itemize}
\chapter{Cíl práce}
Cílem této bakalářské práce je seznámení se s~kompresními algoritmy LZ77, LZ78, LZW a~jejich následná implementace do knihovny SCT (Small Compression Toolkit). Součástí této práce je také jejich následné otestování z~hlediska časové a~paměťové náročnosti, případně porovnání efektivity se stávajícími kompresními algoritmy v~knihovně. Tato knihovna by pak měla ušetřit spoustu práce programátorům, kteří díky ní nebudou muset vymýšlet vlastní implementaci těchto algoritmů.
...
...
@@ -465,7 +468,7 @@ Na počátku algorimu jsou první dva indexy rovny nule, druhý se pak s prvním
Samotné hledání prefixu z aktualního okénka probíhá tak, že vezmu první byte v aktualním okénku a pokusím se ho najít v prohlížecím okénku. Pokud je nalezena shoda, vezmu následující byte z aktualního okénka a porovnám ho s následujícím bytem v prohlížecím okénku. Pokud jsou schodné, postup opakuji, pokud ne, zapamatuji si toto slovo jako kandidáta na nejdelší prefix. A celý postup opakuji dokud neprojdu celé prohlížecí okno a nenjdu největší možný prefix. Prohlížecí okno se postupně zmenšuje díky skutečnosti, že v poli před kandidátem na nejdelší slovo žádné delší nemůže být, protože vím, že nejdelší nalezený prefix v předchozí časti je právě kandidát. Aktualní okénko je stejné, jako na začátku, mění se až po nalezení nejdelšího prefixu.\pagebreak
Po nalezení nejelšího prefixu se všechny indexy posunou o jeho velikost zvětšenou o 1 a do konsumera se uloží triplet, který je tvořen třemi složkami,$$(i, j, b)$$ Kde $i$ je vzdálenost začátku slova od počátku aktualního okénka. Na zapsání je potřeba počet bitů, který udává první parametr zadaný uživatelem. Další složkou je číslo $j$, což je délka prefixu. Počet bitů pro zápis čísla $j$ udává druhý parametr. Poslední složkou je byte $b$, následující po nalezeném prefixu v aktualním okně. Ten má vždy 8 bitů.
Po nalezení nejelšího prefixu se všechny indexy posunou o jeho velikost zvětšenou o 1 a do konsumera se uloží triplet, který je tvořen třemi složkami:$$(i, j, b)$$ Kde $i$ je vzdálenost začátku slova od počátku aktualního okénka. Na zapsání je potřeba počet bitů, který udává první parametr zadaný uživatelem. Další složkou je číslo $j$, což je délka prefixu. Počet bitů pro zápis čísla $j$ udává druhý parametr. Poslední složkou je byte $b$, následující po nalezeném prefixu v aktualním okně. Ten má vždy 8 bitů.
Po kompresi se všechny triplety převedou pomocí třídy \linebreak\classname{TripletToByteConverter} na byty a následně zapíšou do souboru.
...
...
@@ -529,12 +532,48 @@ Na schématu adresářové struktůry můžete vidět třídu LZ78, která inici
\subsection{Parametry}
Metoda má poze dva parametry. Prvním je velikost slovníku, tedy počet frází, které je možné uložit. Podobně jako u metody LZ77 se parametr zadává pomocí exponentu $e$. Velikost slovníku se pak vypočte jako $2^{e-1}$, $e$ je počet bitů potřebný pro zapsání jakehokoliv indexu ze slovníku. Obvykle se $ e $ pohybuje kolem 12-19, což odpovídá 2048 -- 262 144 frází ve slovníku. Druhým parametrem je velikos částí (v bytech). Jeho význam je stejný jako u metody LZ77.
\subsection{Hlavička souboru}
Prvním udajem v hlavičce je parametr $ e $, který udává kolik bitů má číslo indexu v tripletu. Druhým údajem je velikost částí. Důležitost druhého parametru vysvětlím v části o implementaci dekomprese.
\subsection{Implementace komprese}
Kompresi zajišťuje metoda \classname{encode} uvnitř třídy \classname{LZ78TripletCoder}.
Jako struktůru pro slovník jsem se rozhodl použít strom, jehož uzly jsou reprezentováni pomocí třídy \classname{EncodeNode}. Strom je implementovaný tak, že každý uzel má v sobě mapu potomků. To umožňuje velmi rychlé vyhledávání frází.
Slovník reprezentuje metoda \classname{EncodeDictionary}, \classname{compress} vždy načte byte a předá ho slovníku, aby ho zpracoval pomocí metody \classname{processByte}.
Slovník si drží ukazatel na kořen stromu a na aktualní uzel. Kořen sám funguje jako prázdný znak, který má v mapě potomků všech 256 bytů. Jedná se o inicializaci slovníku, která je popsaná v kapitole o analýze algoritmu LZ78 a probíhá v konstruktoru slovníku.
Na začátku je ukazatel na aktualní uzel rovný ukazateli kořenu. Vždy když se zavolá metoda \classname{processByte}, zkontroluje se zda má aktualní uzel v mapě potomků uzel na hodnotě načteného bytu. Pokud ano, ukazatel na aktualní uzel se posune do potomka. Pokud ne, zapíše se triplet $$(i, b)$$ kde $i$ je číslo aktualního uzlu a $b$ je načtený byte. Aktualnímu uzlu se přidá potomek do mapy na hodnotu $b$. Ukazatel na aktualní uzel se posune do kořene.
Tímto způsobem se ve slovníku zjišťuje, jestli obsahuje hledanou frázi. Pokud slovník zjistí, že ji neobsahuje, přidá jí.
V průběhu komprese se kontroluje kolik frází je již ve slovníku. Pokud počet frází dosáhne maxima, které udává parametr, slovník se zamrazí. Komprese pokračuje dál, nicméně slovník se už nezvětšuje.
\subsection{Implementace dekomprese}
Kompresi zajišťuje metoda \classname{decode} uvnitř třídy \classname{LZ78TripletCoder}.
Pro účely testování jsem naimplementoval dva slovníky pro dekompresi. Liší se především tím, s jakou složitostí přidávají a získávají ze slovníku fráze.
Na uplném začátku se načte hlavička a inicializuje slovník jako u komprese. Dále se načte triplet ze souboru. Jeho složky jsou index $ i $ a následující byte $ b $. Index určuje kde se nachází fráze ve slovníku. Zde se implementace liší.
Třída \classname{DecodeList} representuje slovník jako pole frází. Pomocí indexu najdu frázi a tu poté zapíšu.
Třída \classname{DecodeTree} si slovník udržuje jak strom, podobně jako při kompresi. Rozdíl je v tom, že všechny uzli jsou v poli, aby bylo možné hledat uzel na indexu $ i $ v konstatní složitosti. Fráze se pak zrekonstruuje rekurzivně pomocí odkazů na rodiče, kterého každý uzel má. Kořen funguje jako zarážka rekurze protože jeho rodič je null.
Poté co je fráze získaná ze slovníku zapíše se na konec výstupního souboru společně s následujícím bytem $ b $. Následně je na konec slovníku přidána fráze složená ze zapsaného řetězce a bytu $ b $.
V přidávání frází do slovníku se tyto implementace také liší. Zatímco třída \classname{DecodeList} vytvoří novou frázi tak, že zkopíruje řetězec a přidá k němu byte $ b $, třída \classname{DecodeTree} přidá uzlu na indexu $ i $ pouze potomka do mapy s klíčem hodnoty $ b $.
Třída \classname{DecodeList} má tedy složitější přidávání frází, ale jednodušší získávání. U třídy \classname{DecodeTree} je tomu přesně na opak. Nicméně testování ukázalo, že v rychlosti komprese se neprojeví žádné velké rozdíly.
V průběhu dekomprese, algoritmus zaznamenává kolik dat bylo dekódováno. Pokud počet dekódovaných dat dosáhne velikosti druhého parametru z hlavičky, slovník se uvede do počátečního stavu a tvorba slovníku začne od znovu. Tímto zpusobem se správně dekódují jednotlivé části souboru.
\subsection{Spustitelný klient}
Klient metody LZ78 může být spuštěn s těmito přepínači:
...
...
@@ -577,13 +616,23 @@ Na schématu adresářové struktůry můžete vidět třídu LZW, která inicia
\label{dirtree}
\end{figure}
\subsection{Parametry}
\subsection{Hlavička souboru}
\subsection{Parametry a hlavička souboru}
Metoda má totožnou hlavičku i nastavitelné parametry jako LZ78.
\subsection{Implementace komprese}
Kompresi zajišťuje metoda \classname{encode} uvnitř třídy \classname{LZWTripletCoder}.
Od imlementace komprese LZ78 se tato implementace liší pouze v chování při zápisu tripletu, který má pouze jedinou složku: $$(i)$$ Zapisuje totiž pouze index ve slovníku. Poté přidá nového potomka aktualnímu uzlu, s hodnotou posledního načteného bytu. Následně neposune ukazatel do kořene stromu, jako je tomu u LZ78, ale do potomka kořene, který má hodnotu jako načtený byte.
\subsection{Implementace dekomprese}
Dempresi zajišťuje metoda \classname{decode} uvnitř třídy \classname{LZWTripletCoder}.
Třída \classname{DecodeDictionary} ve složce \classname{lzw} representuje slovník, který používá struktůru stromu. Jeho uzly jsou uloženy v poli, aby bylo možné hledat index fráze v konstantní složitosti. Fráze se pak sestavý pomocí rekurzivního procházení přes rodiče až do kořene.
Hlavním rozdíl proti dekompresi LZ78 je, že načtený index z tripletu nemusí být ve slovníku. V takovém případě je dekódovaný řetězec stejný jako poslední přidaná fráze. Do stromu se přidá uzel s hodnotou, která odpovídá prvnímu znaku naposledy přidané fráze. Tento uzel je potomkem naposledy použitého uzlu.
Pokud index ve slovníku je, načte se příslušná fráze. Tato fráze se zapíše do souboru. Do slovníku přidej předchozímu použitému uzlu potomka s hodnotou prvního znaku fráze.