Förlustfri datakompression Patrik Lindberg Institutionen för informationsbehandling Åbo Akademi, 20520 Åbo, Finland E-Post: patlindb@abo.fi Abstrakt Detta papper ger en kort introduktion till förlustfri datakompression samt en presentation av välkända förlustfria kompressionsalgoritmer som frekvent används. De mest centrala begrepp inom ämnet kommer att presenteras eller nämnas. Exempel på innehåll är t.ex Huffman koder och diverse applikationer av dem, familjen av LZ kompressionsmetoder samt en översikt av Burrow Wheeler transformationen. Endel allmän teori på introduktionsnivå presenteras. Slutligen diskuteras kort patentskydden som gäller för nästan alla algoritmer inom ämnet. Klassificering Enligt ACM E. Data E.4 Coding and information theory -Data compaction and compression E.2 Data storage representation Enligt ACM SIG: SIGACT 1
1 Introduktion Behovet av att förmedla och lagra information växer hela tiden. Speciellt förmedlingen av information kräver effektiva metoder. Detta betyder att vi vill sträva till att överföra ett minimalt antal bytes för att få en viss data överförd, vilket inte är en helt trivial uppgift. Ett ganska konkret hinder är faktumet att det inte existerar en enda algoritm som skulle förmå att förminska all data (mera om detta senare), vilket betyder att olika metoder kan uppnå olika resultat på samma data. En metod kan vara mycket effektiv på en typ av data och ineffektiv på en annan, medan det motsatta kan gälla för en annan metod. I allmänhet brukar man skilja mellan två huvudtyper av datakompressionsmetoder: metoder som orsakar förlust och metoder som inte gör det (eng. lossy och lossless). I praktiken innebär detta att då man använder förlustfria metoder så kan en exakt kopia av ursprungsdatan skapas av den komprimerade datan. Om man accepterar förlust i ursprungsdatan så kan kompressionen givetvis göras mycket effektivare. Detta används t.ex för bilder, video och ljud. Tänk t.ex på mpeg, mp3 eller jpeg formaten. I regel använder man förlustfria metoder också då man först applicerat en metod som orsakar förlust. T.ex då man effektivt vill förminska bilder applicerar man först metoder som orsakar förlust. Det spelar ingen roll om vi byter ut två närliggande pixels mot medeltalet av dem, då de för ögat är nästa identiska. När hela bilden är processerad med dylika metoder applicerar man en förlustfri metod. Orsaken till att det är av stor betydelse att uppnå maximal kompression av data är ganska uppenbar. Tänk t.ex ett nätverk vars överföringskapacitet begränsas av en viss mängd bitar per tidsenhet. Om man komprimerar datan som skall överföras så är det ekvivalent med att öka nätverkets maximala kapacitet utan att på något sätt modifiera nätverket fysiskt. Dock behövs hårdvara eller programvara som utför kompressionen, vilket i praktiken innebär att kompressionen ofta görs på en användarmaskin. Idag är det så att det mesta vi överför i våra nätverk är komprimerat på något sätt. Man kanske inte direkt kommer att tänka på det, men det är nästan alltid så att de bildformat som används på nätet är sådana som applicerar kompressionsmetoder. Då man vill överföra data som är lagrat enligt ett format som inte använder sig av kompressionsmetoder så överförs inte den verkliga datan utan ett paket som är väl komprimerat. Paketen skapas med allmänt kända program, vilka omformaterar datan med hjälp av någon välkänd kompressionsalgoritm. Exempel på dessa paket eller filformat är t.ex gz, zip eller rar. Dessa format är mycket kända och vilken vanlig användare som helst kan packa upp datan på egenhand. I detta papper ges en översikt 2
av de vanligaste metoderna som används i dessa välkända kompressions program. Exempel på hur metoderna fungerar förekommer också. 1.1 Olika sätt att se på data Data är tal som representerar olika saker i olika sammanhang. Man kan sedan åstadkomma varierande skriftliga notationer av talen, beroende av vilken talbas man använder. Valet av talbas varierar och görs ofta på basen av bekvämlighetsskäl. Som alla vet så använder vi människor talbasen tio samt att vi har bestämt att datorer internt representerar tal med hjälp av talbasen två. Sedan har vi givetvis massor med olika typer av tal att välja mellan men för att hålla saker enkla så tolkar vi i detta papper all data som positiva hela tal (inklusive talet noll). Man kan se en fil som ett enda enormt tal eller så kan man se på varje bit eller varje byte skilt och tolka filen som en n dimensionell följd av bitar, där n står för antalet bitar som filen innehåller. Det är vanligt att ange storleken eller längden på en data i bytes eller mera approximativt med enheterna KB, MB eller GB. En byte är åtta bitar, alltså representerar varje byte ett värde mellan noll och 255 om man tolkar den som ett tal i talbasen tio. 1.2 Kort om datakomplexitet För en given data går det att bestämma ett tal som beskriver dess komplexitet. Det bör också för varje data existera en minimal data som kan erhållas via en bijektiv transformation av ursprungsdatan. För denna minimala data bör gälla att den har den högsta möjliga komplexiteten och att den inte på något sätt kan göras mindre. Det existerar ett antal metoder för att approximera storleken på den minimala datan, t.ex Shannons entropi ekvation. Det är dock problematiskt då man vill bilda allmän teori gällande detta eftersom transformationen med vilken den minimala datan uppnås inte behöver vara entydig. Kolmogorov var den första som insåg att man kan mäta komplexiteten hos en data, därför talas det ofta om Kolmogorov komplexitet. Ett viktigt samband mellan data komplexitet och kompression är att en data som har högre komplexitet är svårare att komprimera än en data som har lägre komplexitet. Teorier för komplexitets bestämning är ännu inte helt fullständiga och brukar i allmänhet vara mycket invecklade, varför det inte talas mera om dem i denna uppsats. En bra bok om komplexitet är [LiVi97]. 3
Vi skall se lite på hur man kan använda ekvivalensklasser och permutationer för att beskriva en mängd av olika datan. Varje unikt värde i datan bildar en egen ekvivalensklass inom vilken man kan permutera datan godtyckligt utan att den ändrar. Däremot så är ekvivalensklassernas permutationer sinsemellan helt avgörande för hur data ser ut. Man kan genom att permutera de ekvivalensklasser som utgör datan skapa alla olika datan som ekvivalensklasserna kan bygga upp. All data är alltså premutationer av de ekvivalensklasser som bygger upp datan. Ett exempel för att klargöra detta: antag att vi har 3 ekvivalensklasser t.ex, [0, 0, 0], [2, 2] och [5, 5, 5]. Dessa ekvivalensklasser kan t.ex utgöra denna data: [0, 0, 0, 2, 2, 5, 5, 5]. Nu kan vi permutera eller byta plats på nollorna sinsemellan utan att datan ändrar, men permuterar vi däremot ekvivalensklasserna sinsemellan så ändrar datan t.ex [0, 0, 2, 0, 2, 5, 5, 5]. Genom att permutera datan på detta sätt kan vi också komma åt all data som vår mängd av ekvivalensklasser möjligtvis kan utgöra: [0, 0, 0, 2, 2, 5, 5, 5], [0, 0, 2, 0, 2, 5, 5, 5], [0, 0, 2, 2, 0, 5, 5, 5],..., [5, 5, 5, 2, 2, 0, 0, 0]. Man kan också tolka detta enligt följande; För att kunna skapa en data D så räcker det med att veta vilka ekvivalensklasser E som datan består av samt den permutation P som skapar datan. Man kan visa att för P (E) = D gäller att P är entydig. Man kan också inse att om ekvivalensklasserna inte alls är blandade in i varann så kan inte datans komplexitet vara speciellt hög. 2 Teori och beteckningar Följande information ges nu för att förtydliga förklaringarna av de välkända kompressionsalgoritmerna som presenteras i nästa avsnitt. Låt X representera en oändlig följd av tal. Antag att följande gäller: M (N {0}), supm = 255, infm = 0, x X : x M Då gäller för varje ändlig serie av bytes D att D X. Antag att D representerar ursprungsdatan, alltså den data som ges åt kompressionsalgoritmen. Låt f beteckna en implicit definierad bijektiv funktion och antag att D = f(d). Man kan visa att det existerar en invers till f som transformerar D till D. Upp- och nerpackningsprocessen då kan skrivas ner 4
enligt: Nerpackning D f D Uppackning D f 1 D Låt F (x) beteckna frekvensen med vilken x förekommer i en given data D. Då gäller x D F (x) > 0 och y / D F (y) = 0. Låt size(x) vara en funktion som returnerar längden i bytes av x. 2.1 Allmän teori Det existerar ingen algoritm som kan komprimera alla D som är av storleken n = size(d). Antag att en sådan algoritm f existerar. Då gäller för f att D : size(d) = n size(d) > size(d ). Antag nu att size(d) = size(d ) 1. Då har varje D komprimerats med endast en byte, vilket är det minsta möjliga. Då gäller x, y D : x = y. Vilket är en motsägelse eftersom x, y D > f 1 > e. Alltså f kan inte vara bijektiv f är inte en förlustfri kompressionsalgoritm. Tänk också att om en sådan algoritm skulle existera så måste den klara av att förminska all data av längden två bitar till data som har längden en bit, eller en data som har längden en bit till en data som har längden noll, vilket är absurt. Man kan också visa att det inte existerar en rekursiv kompressionsalgoritm som skulle förmå att komprimera data i varje iteration. Man kan också visa att för varje data D så existerar ett teoretiskt maximum för hur mycket denna data går att komprimera. Detta är lätt att inse också om man tänker att om detta vore falskt så skulle det inte finnas något som skulle hindra oss från att komprimera varje data till längden noll. [C00] innehåller information om en jämförelse mellan prestandan på olika välkända kompressionsprogram. 5
3 Välkända kompressionsalgoritmer 3.1 RLE (Run Length Encoding) RLE är den enklaste möjliga datakompressionsmetoden. Man brukar ibland skilja mellan ett antal olika RLE metoder, vilka väsentligen fungerar enligt samma princip. Hela idén bygger på att man i D finner långa sträckor av likadan data. Sedan substituerar man ut dessa enligt ett på förhand bestämt sätt. Hittar man t.ex 100st nollor i D så skriver man i D 0,100 (värde/antal). En liten modifikation till detta har vi en annan variant av RLE-kodning. Då RLE kodar vi endast de värden som är mindre än ett på förhand bestämt värde. Om vi antar att denna gräns är talet 128 så skriver vi de tal som är mindre än 128 rakt in i D medan de övriga kodas enligt värde/antal. Om vi applicerar denna metod på datan D = {2, 5, 4, 150, 150, 150, 8}, så skulle kodningen bli D = {2, 5, 4, 150, 3, 8}. Man kan givetvis byta ut antalet bitar som används för att representera antal-fältet eller ändra på gränserna som bestämmer vilka värden som kodas. Man uppnår ganska varierande resultat på samma data då man gör detta. En annan vanlig tillämpning av RLE-principen använder sig av ett speciellt tecken (eng: escape character) i D för att identifiera en position som följs av värde/antal kodning. Det föråldrade bildformatet pcx använder sig av RLE med varierande effekt på bildstorleken. RLE är ett acceptabelt val för att minska på pcx-bilden då färgdjupet är åtta bitar. Då kan man optimera palettrepresentationen så att den passar bättre in för den valda RLE-kodningen. I grund och botten fungerar RLE-kodning inte så bra eftersom hela principen bygger på det felaktiga antagandet att data innehåller långa följder av likadana värden. Följden av en RLE-kodning kan ofta vara att D > D. 3.2 Substitution, Huffman-koder och aritmetisk kodning Vi vill sträva till att skapa nya, kortare representationer för sådana värden i D som förekommer oftare än andra. Man åstadkommer detta genom att frekvensanalysera D. Då erhåller man en tabell som sedan används för att substituera korta koder åt de värden som förekommer oftast. För att varje unikt värde i D skall få en entydig ny representation i D så krävs det, då D består av x st unika värden, log 2 (x) bitar. Detta är uppenbart, tänk t.ex att log 2 (256) = 8 och 2 8 = 256. Ett binärt träd råkar vara en mycket lämplig 6
struktur för att exemplifiera detta och utveckla teorin vidare. Man kan nu med hjälp av det binära trädet enkelt skapa de nya koderna genom att placera varje x D i ett löv i trädet. Gör man detta så kommer varje nya kod att vara unik. En annan viktig egenskap som de nya värdena kommer att ha är att inget av dem existerar som ett prefix i ett annat (man kallar därför ibland dessa binära träd för prefix träd). Om man skulle skapa ett balanserat träd (vilket är optimalt endast då följande gäller: (1) x D : F (x) = k, alltså alla unika värden förekommer med samma frekvens, (2) antalet unika värden i D är en potens av två) skulle djupet av trädet bli [log 2 (x)] + 1 + a, där [x] betecknar heltalsdelen av x och definieras enligt [x] x < [x] + 1 och a = 0 då [log 2 (x)] N annars är a = 1. Vi exemplifierar detta med endel bilder och förklaringar. I figuren ovan representerar [1], [2], [3] och [4] unika datan. [1] kunde tänkas representera bokstaven a, [2] representerar bokstaven b och så vidare. Enligt trädet ovan så skulle [1] kodas om med värdet 00, [2] blir 01, [3] blir 10 och [4] blir 11. Detta uppnår man då man noterar en nolla då man traverserar trädet åt vänster och en etta då man traverserar åt höger. Observera att inget av dessa förekommer som ett prefix i ett annat. Trädet ovan åstadkommer ännu ingen kompression men det förklarar hur det går till när vi ger nya koder åt datan. Antag att F ([1]) = 500, F ([2]) = 20, F ([3]) = 20 och F ([4]) = 15. Vi ser att datan som [1] identifierar förekommer mycket oftare än de övriga, och vill utnyttja detta. Om vi substituerar datan i fråga enligt figuren ovan så kommer D att vara mindre än D. Detta är möjligt just för att F ([1]) är mycket större än F ([2]), F ([3]) och F ([4]). Koden för [1] i D kommer att vara 1 vilken är hälften kortare än den ursprungliga koden i D. I verkligheten så behandlas flera datavärden än fyra i gången eftersom det oftast är ändamålsenligt, men det framfaller nu att man med denna metod kan åstadkomma kompression. Det är också uppenbart att vissa prefixträd åstadkommer bättre kompression än andra, jämför t.ex de två träden ovan. 7
Metodens nytta framfaller tydligare då vi behandlar ett större antal olika datavärden, där vissa förekommer med hög frekvens och vissa med låg frekvens. Det går att visa att det för en given data D existerar åtminstone ett optimalt prefixträd (oftast existerar det en mängd av optimala prefixträd). Mannen som först presenterade en algoritm för att uppnå ett optimalt prefix träd var Huffman [H52]. Man brukar därför tala om Huffman-koder eller Huffman-träd då man hänvisar till dessa stukturer. Man kan visa att Huffman-kodningen genererar optimal kod endast om frekvensen av varje element i D är en potens av 1/2. En metod som inte lider av denna begränsning är aritmetisk kodning. Då man använder aritmetisk kodning på ett optimalt sätt så är det den effektivaste metoden som existerar för att komprimera data idag. Man kan visa att då längden av D är stor så är den aritmetiska kodningens effektivitet nära 100% av det teoretiska maximumet. En sådan tillämpning involverar mera komplexa metoder som t.ex dynamisk Markov-modellering och andra metoder som används för att förutspå frekvenser inom en delmängd av D. Läs mera i [LiVi97]. Aritmetisk kodning har dock en ganska stor nackdel (förutom patentskydd) som gör att metoden i allmänhet inte används. Problemet är att det kräver väldigt mycket minne och prosessor resurser att utföra kodningen på ett optimalt sätt. Använder man Markov-modelleringen så börjar man med en modellering av ordningen noll som sedan kan utvidgas till ordningen n. Då n går mot oändligheten så är kodningen optimal, men problemet är att resurskraven också går mot oändligheten, vilket inte är acceptabelt. I grunden fungerar aritmetisk kodning så att varje x D tilldelas en delmängd i intervallet [0, 1] R, på basen av den relativa frekvensen med vilken x förekommer i D. Man kan använda statiska tabeller för att bestämma symbolfrekvenserna, men det är mera optimalt att använda statistisk modellering (t.ex Markov-modellering). Som sagt så är hela processen ganska invecklad varför vi inte går vidare med detta i denna uppsats. Följande bild från [FAQ] beskriver dock grafiskt hur kodningen går till då man har två olika datan som vi kallar för X och Y (Den statistiska modelleringen faller lite utanför syftet med detta papper och man kan läsa mera om det i böcker om diskret matematik, t.ex [S94]). 8
3.3 LZ (Lempel, Ziv) Familjen av kompressorer De två mest använda metoderna för att komprimera data idag är LZ77 (se [ZiLe77]) och LZ78. Båda baserar sig på användningen av en data-ordbok (eng: data dictionary). LZ77 är en sekventiell algoritm. D gås alltså igenom från början till slut (=sekventiellt) samtidigt som algoritmen håller reda på de n senaste bytesen i D. Detta kallas allmänt för glidande fönster metoden (eng: sliding window). Om man påträffar en sekvens av bytes som existerar i fönstret så skrivs detta ner i D i form av ett offset/längd par, där offset säger hur många bitar man skall räkna tillbaka i fönstret, varifrån man sedan tar så många bytes som specifieras av längd parametern. I D så identifieras en pekare (som pekar in i fönstret) med en nolla och okomprimerad data med en etta, vilket betyder att okomprimerade bytes kommer att ta 9 bitar istället för 8 i D. Storleken på fönstret är avgörande för resultatet eftersom ett för litet fönster minimerar chansen att hitta identiska strängar och ett för stort fönster genererar för stora pekare. LZ77 fungerar bra för text eftersom det är sannolikt att samma ord förekommer ofta i texten. Vid kompression av text kan man räkna med att LZ77 har i medeltal följande effekt: D.33D, vilket är ett utmärkt resultat. Vid implementering av LZ77 används oftast binära träd och hashtabeller så att sökningen i det glidande fönstret kan göras så effektivt som möjligt. Många applikationer som använder sig av LZ77 kör ännu en nivå av 9
kompression på D (som resulterar från applicerandet av LZ77 på D). Detta lönar sig eftersom en frekvensanalys av D (från LZ77) visar att frekvenserna kan variera ganska mycket, vilket medför att det är sannolikt att t.ex Huffman-kodning ytterligare kan minska på storleken. LZ78 är en metod som baserar sig på ett indexerings system, där data sekvenser identifieras på basen av ett index i en data-ordbok (data dictionary). Data ordboken behöver inte sparas explicit i D utan uppackningsproceduren kommer att vara helt oberoende av den. Man kan observera att både LZ77 och LZ78 i grunden åstadkommer kompression genom substitution såsom de metoder som diskuterats tidigare också har gjort. Det är också av intresse att veta att man kan visa att data-ordboksmetoden i LZ78 är ekvivalent med glidande fönster metoden i LZ77 och att dessa båda kan visas vara ekvivalenta med substitutions metoden. Det existerar massor med olika algoritmer som baserar sig på LZ77 och LZ78. Dessa varianter av LZ77 och LZ78 skiljer sig i regel åt endast via de data strukturer som används i implementeringen eller i representationen av data (små optimeringar och specialfallsbeaktanden förekommer också). Exempel på varianter av LZ77 är t.ex LZR, LZSS, LZB och LZH. Varianter av LZ78 innehåller LZW, LZC, LZT, LZMW, LZJ. Många flera existerar. Som ett konkret exempel kan nämnas att zip och unzip programmen baserar sig på LZH (Lempel-Ziv-Huffman) vilket i praktiken betyder att LZ77 appliceras först och sedan appliceras Huffman-kodning som en andranivå kompression. UNIX compress metoden baserar sig däremot på den välkända LZW metoden, vilken introduceras nedan. LZW (Lempel-Ziw Welch) metoden av Terry Welch [W84] var ursprungligen menad för att operera som inbyggd programvara i ett kontrollkort som var menat för att styra ett sekundärminne. Det var meningen att detta skulle öka prestandan hos sekundärminnet. LZW-algoritmen fungerar på basen av antagandet att en sekvens av bytes kommer att förekomma flera gånger i D. Alltid när en viss byte kombination hittas på nytt så markeras detta i en tabell (i data-ordboken). Den ursprungliga tabellen innehåller endast element av längden ett. Hela nerpackningsproceduren går till så att man tar en byte i gången från D tills man har hittat en sträng som inte finns i tabellen. När man gjort detta så sparar man den nya strängen som ett nytt element i tabellen och fortsätter vidare i D. Vanligtvis så brukar implementeringar av LZW begränsa antalet element i tabellen. När denna gräns av element nås så tilläggs inte nya element till tabellen mera utan nerpackningsproceduren fortsätter att använda de element som finns tills man har gått igenom alla bytes i D. 10
3.4 BW (Burrows, Wheeler) Algoritmen Burrow-Wheeler-transformationen är egentligen inte en komressionsmetod men har utmärkta egenskaper som kan utnyttjas mycket effektivt då man komprimerar data. Transformationen i sig åstadkommer inte någon kompression utan dess syfte är att ordna om datan i en sådan form som är mycket lämplig för kompression. Prestandan är nära den som erhålls vid aritmetisk kodning medan resursanvändningen är på LZ-kodarnas nivå. Det är också värt att påpeka att det inte existerar några patenter som hänvisar direkt till denna metod, men eftersom BW-transformationen måste användas i samband med övriga kompressions metoder så minskar betydelsen av detta. BW-transformationen har inte blivit en standard för dagens datakomprimering utan den domineras ännu av LZ-kompressorer och Huffman-kodare. BW-transformationen utförs på en delmängd av D i gången. I [BuWh94] rekommenderas att varje delmängd som tas från D bör vara av storleken 1KB eller större, medan man idag kan konstatera att implementationer använder sig av en block storlek av minst 50KB. Antag att för varje x D gäller att x X, där X är en ordnad mängd. För varje delmängd d av D utförs följande. Först skapas ett kvadratiskt block så att följande gäller: a : [x, a] = [y, a], x = y, 0 a < size(x) = n. Sedan utförs b steg aritmetisk shift left för rad b, börjande med rad 0 och till sist rad (n 1) Efter detta så gäller att varje element i blocket också finns i d och d är en delmängd av D. Det gäller också att diagonalen [n x 1, x], där 0 x < n, består av likadana element och alla dessa element hör givetvis fortfarande till d. Sedan ordnar man om raderna så att kolumnen K0 innehåller samma ordningsrelation som X. Ett exempel för att klargöra det hela följer. Exempel: d D : d = {01, 03, 02, 07, 05, 11} X = N Steg 1 Steg 2 Steg 3 ---K0,K1,K2,K3,K4,K5 ---K0,K1,K2,K3,K4,K5 ---K0,K1,K2,K3,K4,K5 R0:01,03,02,07,05,11 R0:01,03,02,07,05,11 R0:01,03,02,07,05,11 R1:01,03,02,07,05,11 R1:03,02,07,05,11,01 R2:02,07,05,11,01,03 R2:01,03,02,07,05,11 R2:02,07,05,11,01,03 R1:03,02,07,05,11,01 R3:01,03,02,07,05,11 R3:07,05,11,01,03,02 R4:05,11,01,03,02,07 R4:01,03,02,07,05,11 R4:05,11,01,03,02,07 R3:07,05,11,01,03,02 R5:01,03,02,07,05,11 R5:11,01,03,02,07,05 R5:11,01,03,02,07,05 Resultatet av en BW-transformation är alltid den sista kolumnen (K(n 1)) samt indexet av det elementet i den sista kolumnen som innehåller det första elementet i D. Notera att det inte spelar någon roll om vi i steg två 11
roterar raderna åt höger eller vänster. Den K(n 1) som skapas då man i steg två roterar raderna åt vänster är alltså alltid identisk med den K(n 1) som skapas då man roterar raderna åt höger. Teorin ändras lite om man väljer att rotera elementen åt höger, så vi antar nu att elementen roteras åt vänster. I exemplet ovan är n 1 = 5, K5 = {11, 03, 01, 07, 02, 05} och index = 2. Indexet är lätt att bestämma eftersom R0 = d vilket medför att sista elementen i R1 alltid är det sökta elementet. Kom ihåg att R(n + 1) skapas genom att rotera Rn ett steg åt vänster och första elementet i R0 placeras sist i R1. Eftersom den sista kolumnen och indexet är vad vi får ut från transformationen måste det gälla att vi med denna information kan återskapa ursprungsdatan. Man kan med den information vi har skapa en lista av index som säger hur vi skall flytta elementen i vektorn för att återskapa ursprungsdatan. Detta går till såhär: Steg 1 Steg 2 ---K5,K0 R0:11,01 (K5,R0) = (K0,R5) -> T[5]=0 R1:03,02 (K5,R1) = (K0,R2) -> T[2]=1 R2:01,03 (K5,R2) = (K0,R0) -> T[0]=2 R3:07,05 (K5,R3) = (K0,R4) -> T[4]=3 R4:02,07 (K5,R4) = (K0,R1) -> T[1]=4 R5:05,11 (K5,R5) = (K0,R3) -> T[3]=5 -> T=2,4,1,5,3,0 Steg ett går ut på att återskapa K0 från K(n 1). Eftersom vi vet att K0 är sorterad så utför vi en sortering på K(n 1) för att generera K0. I steg två så genereras transformations vektorn. Denna är entydig och man kan med T skapa den ursprungliga datan. I verkligheten så finns det flera element som är lika i K(n 1) och K0. Då väljs alltid det element som är högst upp i K0 och inte är valt från tidigare. Eftersom dekodnings processen inte direkt är intuitiv så är det lättaste sättet att förstå hur dekodaren fungerar en studie över hur den opererar steg för steg på vårt exempel. Vi använder följande C kod för att skapa debug utskrift för dekodaren: 12
#define BYTE unsigned char void BWTDecode(int index,byte* T, BYTE* Kn, int range) { int lp; printf( Primaty Index = %d\n,index); for (lp = 0 ; lp < range ; lp++) { printf("kn[%d]=%d\n", index, Kn[ index ]); printf("index = T[%d] = %d\n",index,t[index]); index = T[ index ]; } } Funktionen är en modifierad version av ett kodexempel från N[96]. N[96] innehåller också fullständig källkod för att kunna skapa BW-transformerad komprimerad data. Som följande anropas BWTDecode med parametrarna från vårt exempel så ser vi hur ursprungs datan genereras. BYTE T[6]=02,04,01,05,03,00; BYTE K5[6]=11,03,01,07,02,05; BWTDecode(2,T,K5,6); ->utskriften av programmet: Primaty Index = 2 Kn[2]=1 index = T[2] = 1 Kn[1]=3 index = T[1] = 4 Kn[4]=2 index = T[4] = 3 Kn[3]=7 index = T[3] = 5 Kn[5]=5 index = T[5] = 0 Kn[0]=11 index = T[0] = 2 13
Vi ser tydligt hur koden hoppar fram och tillbaka i T för att få fram elementen i den ordning som de var från början. En detaljerad förklaring av BW transformationen finns i [BuWh94]. 4 Patenter Det finns massor av patenter som gäller för varje välkänd datakompressionsalgoritm. Patenterna gäller endast i USA och inte i den övriga världen (just nu), men ofta har detta den följden, att t.ex företag inte kan använda sig av dessa algoritmer. Massor av patenter är i konflikt med varandra och många syftar till sådant som inte är teoretisk möjligt. Det existerar en patent som diffust beskriver algoritm med ord i stil med: denna patent gäller för alla algoritmer som kan minska på data och en annan som säger ungefär såhär: en algoritm som minskar varje data som ges åt den (vilket inte är teoretisk möjligt). IBM äger över 30 patenter som syftar på olika implementationer av aritmetisk kodning. I varje fall så är det viktigt att vara medveten om detta, så att nästa effektiva kompressionsalgoritm som uppfinns publiceras under ett allmänt licens. 14
Källor [LiVi97] [W99] [H52] [W84] [S94] [ZiLe77] [N96] [BuWh94] [C00] [FAQ] Ming Li, Paul Vitanyi, An Introduction to Kolmogorov Complexity and Its Applications, Second Edition, Springer, 1997. Mark Allen Weiss, Data Structures and Algorithm analysis in Java, Addison-Wesley, 1999. D. Huffman, A Method for the Construction of Minimum-Redundancy Codes, 1952 Terry A. Welch, A Technique for High Performance Data Compression, IEEE Computer, 17(6), June 1984, pp. 8-19 William J. Stewart, Introduction to the Numerical Solution of Markov Chains, Princeton University Press, 1994. J. Ziv and A. Lempel, A Universal Algorithm for Sequential Data Compression, IEEE Transactions on Information Theory, Vol. IT-23, No3, May 1977, pp.337-343. Mark Nelson, Data Compression with the Burrows-Wheeler Transform Dr. Dobb s Journal, Sept. 1996 eller http://www.dogma.net/markn/articles/bwt/bwt.htm Michael Burrows, David Wheeler, A Block-Sorting Lossless Data Compression Algorithm Digital Systems Research Center, Research report, May 10, 1994 A comparison between lossless compression programs, sept. 2000, http://geocities.com/eri32/texts21.html Compression FAQ, http://www.faqs.org/faqs/compression-faq/ 15