Popis implementace

Implementace algoritmu ACB je rozdělena na dvě části: knihovnu libacb (standardně překládanou jako sdílená i statická), která obsahuje pět základních tříd, a samostatné ukázkové programy azip a aunzip demonstrující jejich použití (ty se standardně linkují staticky, aby nebyly závislé na přítomnosti sdílené knihovny libacb v systému).

Úroveň implementace algoritmu by se dala nejlépe ohodnotit jako proof-of-concept — třídy knihovny libacb provedou korektní kompresi a dekompresi libovolných dat, ovšem pro praktické nasazení algoritmu je potřeba jej ještě vylepšit o tyto důležité vlastnosti:

Pro zkoumání vlastností algoritmu a porovnávání různých variant implementace je knihovna libacb navržena maximálně modulárně, všechny metody tříd jsou virtuální pro snadnou náhradu jejich kódu alternativním. Přes omezení uvedená výše je však předkládané zpracování algoritmu plně funkční a na mnoha druzích vstupních dat (především delší textové soubory) skutečně dosahuje rozumné (byť suboptimální) míry komprese.

1. Přehled

Adresářový strom projektu vypadá takto:

Kořenový adresář obsahuje především konfigurační skript configure (vygenerovaný pomocí utility Autoconf) a po jeho provedení také hlavní soubor Makefile. Projekt se překládá a instaluje běžným způsobem:

./configure
make
make install
				

Adresář doc obsahuje dokumentaci projektu, include po překladu hlavičkové soubory potřebné pro používání knihovny libacb cílovými programy, adresář lib po překladu sdílenou a statickou podobu knihovny libacb (libacb.so, libacb.a), adresář sample obsahuje zdrojové soubory ukázkových programů azip a aunzip a po překladu také jejich spustitelné tvary (ty jsou přeloženy staticky, aby je bylo možné použít i bez nainstalování sdílené libacb.so do systému) a konečně adresář src obsahuje samotné zdrojové soubory knihovny libacb.

Příkaz make install (pod uživatelem root) nainstaluje soubory z adresářů include a lib do systému (tedy typicky do /usr/local/include a /usr/local/lib).

2. libacb

Knihovna implementuje pět tříd. Třída Vocabulary je struktura reprezentující aktuální stav algoritmu komprese nebo dekomprese, třídy Encoder a TripletEncoder slouží pro kompresi a třídy Decoder a TripletDecoder pro dekompresi.

2.1. Vocabulary

Aktuální stav komprese a dekomprese je definován obsahem slovníku, obsahem search bufferu a look-ahead bufferu. Třída Vocabulary spravuje tyto datové struktury a implementuje metody pro jejich manipulaci.

class Vocabulary {
    public:
        Vocabulary();
        virtual ~Vocabulary();
	
        virtual bool Exhausted(void) const;
        virtual void Input(const char * input, const size_t cnt);
        virtual void Compare(size_t & distance, bool & sign, size_t & match, char & next, bool & complete);
        virtual char * GetContent(const size_t distance, const bool sign, const size_t match, const char next,
                                  const bool complete, size_t & size);
}
					
Vocabulary::Vocabulary()
Konstruktor vytvoří třídu s prázdným slovníkem a prázdnými search a look-ahead buffery.
Vocabulary::Exhausted()
Metoda vrací true, nejsou-li již v look-ahead bufferu žádné znaky, které by se daly zkomprimovat.
Vocabulary::Input()
Metoda přidává do look-ahead bufferu znaky z pole znaků. Tyto znaky budou pozdeji pomocí metody Vocabulary::Compare() zkomprimovány.
Vocabulary::Compare()
Obsahuje-li look-ahead buffer znaky, potom metoda porovná aktuální kontext (search buffer zprava doleva) s kontexty slovníku, najde nejlepší shodu look-ahead bufferu s contenty slovníku a na výstup dá jednak rozdíl těchto dvou pozic (distance se znaménkem sign), délku nejlepší shody contentu (match) a první rozdílný znak mezi look-ahead bufferem a contentem (next — je-li shoda úplná a žádný rozdílný znak neexistuje, obsahuje complete hodnotu true).
Metoda zároveň přidá do slovníku kontexty a contenty, které odpovídají právě zakodované posloupnosti znaků.
Je-li na začátku provádění metody search buffer prázdný (tj. zatím nebyl zakódován žádný znak), je první znak z look-ahead bufferu vrácen zpět jako posloupnost s nulovou shodou se slovníkem, je přidán do search bufferu a slovník zůstává prázdný (dojde tedy k zakódování iniciálního znaku).
Vocabulary::GetContent()
Metoda sloužící k dekompresi. Nejprve se porovnáním najde aktuální pozice search bufferu (zprava doleva) v aktuálním slovníku a na základě této hodnoty a vstupní hodnoty distance (a sign) se určí content slovníku, ve kterém byla při kompresi nalezena shoda délky match. Tyto shodné znaky jsou z příslušného contentu vykopírovány jednak do search bufferu a také na výstup metody. Obsahují-li komprimovaná data také informaci o prvním neshodném znaku next (v případě, že complete je false), je také přidán do search bufferu a vrácen na výstup.
Současně se zvětšením search bufferu je příslušně aktualizován slovník (analogicky jako při kompresi).

Search a look-ahead buffer jsou uvnitř třídy reprezentovány jako jediné dynamicky alokované pole znaků (rozhraní mezi search a look-ahead oblastí určuje délka search bufferu). Při přidávání znaků pomocí Vocabulary::Input() se toto pole zvětšuje pomocí funkce realloc().

Slovník je reprezentován jako seznam (pomocí STL šablony list) obsahující index v bufferu, ktery určuje rozhraní mezi částí kontextu a contentu.

Privátní metody třídy implementují výše uvedené operace, tedy nalezení předpokládané pozice search bufferu v kontextové části slovníku, určení shody look-ahead bufferu s contentovou částí slovníku a obohacení slovníku o nové znaky (přesunutí znaku z look-ahead do search bufferu). Jedna metoda slouží pro výpis aktuálního slovníku na chybový výstup v případě ladění.

2.2. TripletEncoder

Třída ukládající data po kompresi do výstupního streamu. Zkomprimovaná data představují posloupnost trojic [(s)d, m, n], přičemž d je rozdíl nalezeného kontextu a contentu (s je znaménko), m je délka shody look-ahead bufferu a contentu a n je první neshodující se znak.

Trojice se do výstupního streamu ukládají za sebou po jednotlivých bitech a další úspory se dosahuje tím, že je-li m=0 (žádná shoda), potom se místo hodnot d a m ukládá jen jednobitový příznak. Analogicky pokud je shoda look-ahead bufferu a a contentu úplná, neukládá se do streamu neshodující se znak. To dává celkem tři možnosti uložení trojice (čtvrtá možnost bez shody a bez znaku nastat nemůže):

1sdm0n
1sdm1
00n
class TripletEncoder {
     public:
          TripletEncoder(std::ostream & _outs);
          virtual ~TripletEncoder();
	 
          virtual void Encode(const size_t distance, const bool sign, const size_t match, const char next,
                              const bool complete);
          virtual void Flush(void);
}
					
TripletEncoder::TripletEncoder()
Konstruktor, jehož parametr je výstupní stream, do kterého se budou ukládat zakódované trojice.
TripletEncoder::Encode()
Metoda, která zakóduje jednu trojici. Bity se ve výstupním streamu nemusí objevit hned, ale až po zaplnění interního bufferu.
TripletEncoder::Flush()
Metoda pro vyprázdění interního bufferu do výstupního streamu. Dosud vygenerované bity jsou zarovnány na hranici bytů, znaků apod. (podle platformy) nulovými bity.

2.3. TripletDecoder

Třída má doplnkovou funkci k třídě TripletDecoder, tedy ze vstupního streamu čte bitově jednotlivé trojice a dekóduje je do uspořádání [(s)d, m, n], které lze poté přímo použít pro dekompresi.

class TripletDecoder {
    public:
        TripletDecoder(std::istream & _ins);
        virtual ~TripletDecoder();
	
        virtual bool Decode(size_t & distance, bool & sign, size_t & match, char & next, bool & complete);
        virtual void Fill(void);
}
					
TripletDecoder::TripletDecoder()
Konstruktor, jehož parametr je vstupní stream obsahující trojice.
TripletDecoder::Decode()
Metoda má návratovou hodnotu true, pokud byla ze vstupního streamu korektně přečtena trojice.
TripletDecoder::Flush()
Metoda pro smazání a opětovné naplnění interního bufferu ze vstupního streamu. Volá se explicitně jen v případě, že aktuální data ve vstupním proudu nemají žádný vztah k předchozím (opatření proti tomu, aby v interním bufferu třídy nebyla ještě zbývající nezpracovaná předchozí data).

2.4. Encoder

Třída provádějící s použitím Vocabulary a TripletEncoder samotný algoritmus komprese ACB.

class Encoder {
    public:
        Encoder(Vocabulary & _voc, TripletEncoder & _trip, std::istream & _ins);
        virtual ~Encoder();

        virtual const Encoder & operator+=(const size_t cnt);
}
					
Encoder::Encoder()
Konstruktor, který má jako parametr objekt třídy Vocabulary (implementující výpočetní část kompresního algoritmu), objekt třídy TripletEncoder (kódující výstup algoritmu do výstupního streamu) a vstupní stream.
Encoder::operator+=
Přičtením čísla pomocí tohoto operátoru se provede komprese daného počtu znaků ze vstupního streamu. Určený počet znaků je nejprve přečten ze streamu, uložen do look-ahead bufferu objektu Vocabulary a poté postupně, dokud je look-ahead buffer neprázdný, se pomocí Vocabulary::Compare() provede komprese jistého počtu znaků do trojice [(s)d, m, n] a ta se následně pomocí metody TripletEncoder::Encode() uloží do výstupního streamu.

2.5. Decoder

Třída provádějící s použitím Vocabulary a TripletDecoder algoritmus dekomprese ACB.

class Decoder {
    public:
        Decoder(Vocabulary & _voc, TripletDecoder & _trip, std::ostream & _outs);
        virtual ~Decoder();

        virtual const Decoder & operator+=(const size_t cnt);
}
					
Decoder::Decoder()
Konstruktor, který má jako parametr objekt třídy Vocabulary (implementující výpočetní část dekompresního algoritmu), objekt třídy TripletDecoder (dekódující trojice ze vstupního streamu) a výstupní stream.
Decoder::operator+=
Přičtením čísla pomocí tohoto operátoru se provede dekomprese daného počtu trojic ze vstupního streamu a do výstupního streamu se uloží znaky, které vznikly touto dekompresí.
Ze vstupního streamu jsou pomocí TripletDecoder::Decode() čteny trojice [(s)d, m, n] a ty jsou použity pro volání metody Vocabulary::GetContent(), která přímo generuje znaky výstupu, jež jsou poté uloženy do výstupního streamu.

3. azip, aunzip

Ukázkové programy zcela přímočaře využívají knihovnu libacb. Původní (resp. komprimovaná) data jsou čtena ze standardního vstupu a na standardní výstup jsou posílana data komprimovaná (resp. dekomprimovaná).

Zdrojový kód obou ukázkových programů je téměř samovysvětlující:

#include <iostream>
#include <acb/acb.h>

int main(int argc, char * argv[]) {
    ACB::Vocabulary vocabulary;
    ACB::TripletEncoder tenc(std::cout);
    ACB::Encoder encoder(vocabulary, tenc, std::cin);

    while (!std::cin.eof())
	    encoder += 10240;

    return 0;
}