10 Lempel-Ziv Compression

\(\newcommand{\footnotename}{footnote}\) \(\def \LWRfootnote {1}\) \(\newcommand {\footnote }[2][\LWRfootnote ]{{}^{\mathrm {#1}}}\) \(\newcommand {\footnotemark }[1][\LWRfootnote ]{{}^{\mathrm {#1}}}\) \(\let \LWRorighspace \hspace \) \(\renewcommand {\hspace }{\ifstar \LWRorighspace \LWRorighspace }\) \(\newcommand {\TextOrMath }[2]{#2}\) \(\newcommand {\mathnormal }[1]{{#1}}\) \(\newcommand \ensuremath [1]{#1}\) \(\newcommand {\LWRframebox }[2][]{\fbox {#2}} \newcommand {\framebox }[1][]{\LWRframebox } \) \(\newcommand {\setlength }[2]{}\) \(\newcommand {\addtolength }[2]{}\) \(\newcommand {\setcounter }[2]{}\) \(\newcommand {\addtocounter }[2]{}\) \(\newcommand {\arabic }[1]{}\) \(\newcommand {\number }[1]{}\) \(\newcommand {\noalign }[1]{\text {#1}\notag \\}\) \(\newcommand {\cline }[1]{}\) \(\newcommand {\directlua }[1]{\text {(directlua)}}\) \(\newcommand {\luatexdirectlua }[1]{\text {(directlua)}}\) \(\newcommand {\protect }{}\) \(\def \LWRabsorbnumber #1 {}\) \(\def \LWRabsorbquotenumber "#1 {}\) \(\newcommand {\LWRabsorboption }[1][]{}\) \(\newcommand {\LWRabsorbtwooptions }[1][]{\LWRabsorboption }\) \(\def \mathchar {\ifnextchar "\LWRabsorbquotenumber \LWRabsorbnumber }\) \(\def \mathcode #1={\mathchar }\) \(\let \delcode \mathcode \) \(\let \delimiter \mathchar \) \(\def \oe {\unicode {x0153}}\) \(\def \OE {\unicode {x0152}}\) \(\def \ae {\unicode {x00E6}}\) \(\def \AE {\unicode {x00C6}}\) \(\def \aa {\unicode {x00E5}}\) \(\def \AA {\unicode {x00C5}}\) \(\def \o {\unicode {x00F8}}\) \(\def \O {\unicode {x00D8}}\) \(\def \l {\unicode {x0142}}\) \(\def \L {\unicode {x0141}}\) \(\def \ss {\unicode {x00DF}}\) \(\def \SS {\unicode {x1E9E}}\) \(\def \dag {\unicode {x2020}}\) \(\def \ddag {\unicode {x2021}}\) \(\def \P {\unicode {x00B6}}\) \(\def \copyright {\unicode {x00A9}}\) \(\def \pounds {\unicode {x00A3}}\) \(\let \LWRref \ref \) \(\renewcommand {\ref }{\ifstar \LWRref \LWRref }\) \( \newcommand {\multicolumn }[3]{#3}\) \(\require {textcomp}\) \(\newcommand {\intertext }[1]{\text {#1}\notag \\}\) \(\let \Hat \hat \) \(\let \Check \check \) \(\let \Tilde \tilde \) \(\let \Acute \acute \) \(\let \Grave \grave \) \(\let \Dot \dot \) \(\let \Ddot \ddot \) \(\let \Breve \breve \) \(\let \Bar \bar \) \(\let \Vec \vec \) \(\renewcommand {\vec }{\boldsymbol }\) \(\newcommand {\Edge }{\ensuremath {\,\textemdash \,}}\) \(\newcommand \Const [1]{\text {\textsf {#1}}}\) \(\DeclareMathOperator {\lerp }{lerp}\) \(\DeclareMathOperator {\bitlen }{bitlen}\) \(\DeclareMathOperator {\sign }{sign}\) \(\newcommand {\I }{\mathrm {i}}\) \(\newcommand \AND {\mathbin {\&}}\) \(\newcommand \OR {\mathbin {|}}\) \(\newcommand \XOR {\mathbin {{}^{\wedge }}}\) \(\newcommand \shl {\ll }\) \(\newcommand \shr {\ggg }\) \(\newcommand \asr {\gg }\) \(\newcommand \NOT {\ensuremath {\mathord {\sim }}}\) \(\newcommand {\isep }{\mathrel {{.}\,{.}}}\) \(\newcommand {\Id }[1]{\mathit {#1}}\) \(\newcommand {\const }[1]{\mathsf {#1}}\) \(\newcommand {\algorithmname }[1]{\text {\textsc {#1}}}\) \(\newcommand {\bits }[1]{\text {#1}}\) \(\newcommand {\hexa }[1]{\mathtt {0x#1}}\) \(\newcommand {\num }[1]{#1}\) \(\newcommand {\qed }{\quad \square }\) \(\newcommand {\idiv }[2]{\lfloor #1/#2\rfloor }\) \(\newcommand \attribdot {\ensuremath {\mkern 1.5mu.\mkern 1.5mu}}\) \(\newcommand \attribxr [2]{#1\attribdot \text {#2}}\) \(\newcommand \attribir [2]{\Id {#1}\attribdot \text {#2}}\) \(\newcommand \attribii [2]{\Id {#1}\attribdot \Id {#2}}\) \(\newcommand \textsc [1]{#1}\) \(\require {colortbl}\) \(\let \LWRorigcolumncolor \columncolor \) \(\renewcommand {\columncolor }[2][named]{\LWRorigcolumncolor [#1]{#2}\LWRabsorbtwooptions }\) \(\let \LWRorigrowcolor \rowcolor \) \(\renewcommand {\rowcolor }[2][named]{\LWRorigrowcolor [#1]{#2}\LWRabsorbtwooptions }\) \(\let \LWRorigcellcolor \cellcolor \) \(\renewcommand {\cellcolor }[2][named]{\LWRorigcellcolor [#1]{#2}\LWRabsorbtwooptions }\) \(\newcommand {\tcbset }[1]{}\) \(\newcommand {\tcbsetforeverylayer }[1]{}\) \(\newcommand {\tcbox }[2][]{\boxed {\text {#2}}}\) \(\newcommand {\tcboxfit }[2][]{\boxed {#2}}\) \(\newcommand {\tcblower }{}\) \(\newcommand {\tcbline }{}\) \(\newcommand {\tcbtitle }{}\) \(\newcommand {\tcbsubtitle [2][]{\mathrm {#2}}}\) \(\newcommand {\tcboxmath }[2][]{\boxed {#2}}\) \(\newcommand {\tcbhighmath }[2][]{\boxed {#2}}\) \(\newcommand {\toprule }[1][]{\hline }\) \(\let \midrule \toprule \) \(\let \bottomrule \toprule \) \(\def \LWRbooktabscmidruleparen (#1)#2{}\) \(\newcommand {\LWRbooktabscmidrulenoparen }[1]{}\) \(\newcommand {\cmidrule }[1][]{\ifnextchar (\LWRbooktabscmidruleparen \LWRbooktabscmidrulenoparen }\) \(\newcommand {\morecmidrules }{}\) \(\newcommand {\specialrule }[3]{\hline }\) \(\newcommand {\addlinespace }[1][]{}\) \(\newcommand {\LWRsubmultirow }[2][]{#2}\) \(\newcommand {\LWRmultirow }[2][]{\LWRsubmultirow }\) \(\newcommand {\multirow }[2][]{\LWRmultirow }\) \(\newcommand {\mrowcell }{}\) \(\newcommand {\mcolrowcell }{}\) \(\newcommand {\STneed }[1]{}\) \(\newcommand {\LWRldelimtwo }[1][]{\text {#1}~\LWRbigdelim }\) \(\newcommand {\LWRldelimone }[2][]{\LWRldelimtwo }\) \(\def \ldelim #1#2{\def \LWRbigdelim {#1}\LWRldelimone }\) \(\newcommand {\LWRrdelimtwo }[1][]{\LWRbigdelim ~\text {#1}}\) \(\newcommand {\LWRrdelimone }[2][]{\LWRrdelimtwo }\) \(\def \rdelim #1#2{\def \LWRbigdelim {#1}\LWRrdelimone }\) \(\let \symnormal \mathit \) \(\let \symliteral \mathrm \) \(\let \symbb \mathbb \) \(\let \symbbit \mathbb \) \(\let \symcal \mathcal \) \(\let \symscr \mathscr \) \(\let \symfrak \mathfrak \) \(\let \symsfup \mathsf \) \(\let \symsfit \mathit \) \(\let \symbfsf \mathbf \) \(\let \symbfup \mathbf \) \(\newcommand {\symbfit }[1]{\boldsymbol {#1}}\) \(\let \symbfcal \mathcal \) \(\let \symbfscr \mathscr \) \(\let \symbffrak \mathfrak \) \(\let \symbfsfup \mathbf \) \(\newcommand {\symbfsfit }[1]{\boldsymbol {#1}}\) \(\let \symup \mathrm \) \(\let \symbf \mathbf \) \(\let \symit \mathit \) \(\let \symsf \symsfit \) \(\let \symtt \mathtt \) \(\let \symbffrac \mathbffrac \) \(\newcommand {\mathfence }[1]{\mathord {#1}}\) \(\newcommand {\mathover }[1]{#1}\) \(\newcommand {\mathunder }[1]{#1}\) \(\newcommand {\mathaccent }[1]{#1}\) \(\newcommand {\mathbotaccent }[1]{#1}\) \(\newcommand {\mathalpha }[1]{\mathord {#1}}\) \(\def\Alpha{\unicode{x1D6E2}}\) \(\def\Beta{\unicode{x1D6E3}}\) \(\def\Gamma{\unicode{x1D6E4}}\) \(\def\Digamma{\mathit{\unicode{x03DC}}}\) \(\def\Delta{\unicode{x1D6E5}}\) \(\def\Epsilon{\unicode{x1D6E6}}\) \(\def\Zeta{\unicode{x1D6E7}}\) \(\def\Eta{\unicode{x1D6E8}}\) \(\def\Theta{\unicode{x1D6E9}}\) \(\def\Vartheta{\unicode{x1D6F3}}\) \(\def\Iota{\unicode{x1D6EA}}\) \(\def\Kappa{\unicode{x1D6EB}}\) \(\def\Lambda{\unicode{x1D6EC}}\) \(\def\Mu{\unicode{x1D6ED}}\) \(\def\Nu{\unicode{x1D6EE}}\) \(\def\Xi{\unicode{x1D6EF}}\) \(\def\Omicron{\unicode{x1D6F0}}\) \(\def\Pi{\unicode{x1D6F1}}\) \(\def\Rho{\unicode{x1D6F2}}\) \(\def\Sigma{\unicode{x1D6F4}}\) \(\def\Tau{\unicode{x1D6F5}}\) \(\def\Upsilon{\unicode{x1D6F6}}\) \(\def\Phi{\unicode{x1D6F7}}\) \(\def\Chi{\unicode{x1D6F8}}\) \(\def\Psi{\unicode{x1D6F9}}\) \(\def\Omega{\unicode{x1D6FA}}\) \(\def\alpha{\unicode{x1D6FC}}\) \(\def\beta{\unicode{x1D6FD}}\) \(\def\varbeta{\unicode{x03D0}}\) \(\def\gamma{\unicode{x1D6FE}}\) \(\def\digamma{\mathit{\unicode{x03DD}}}\) \(\def\delta{\unicode{x1D6FF}}\) \(\def\epsilon{\unicode{x1D716}}\) \(\def\varepsilon{\unicode{x1D700}}\) \(\def\zeta{\unicode{x1D701}}\) \(\def\eta{\unicode{x1D702}}\) \(\def\theta{\unicode{x1D703}}\) \(\def\vartheta{\unicode{x1D717}}\) \(\def\iota{\unicode{x1D704}}\) \(\def\kappa{\unicode{x1D705}}\) \(\def\varkappa{\unicode{x1D718}}\) \(\def\lambda{\unicode{x1D706}}\) \(\def\mu{\unicode{x1D707}}\) \(\def\nu{\unicode{x1D708}}\) \(\def\xi{\unicode{x1D709}}\) \(\def\omicron{\unicode{x1D70A}}\) \(\def\pi{\unicode{x1D70B}}\) \(\def\varpi{\unicode{x1D71B}}\) \(\def\rho{\unicode{x1D70C}}\) \(\def\varrho{\unicode{x1D71A}}\) \(\def\sigma{\unicode{x1D70E}}\) \(\def\varsigma{\unicode{x1D70D}}\) \(\def\tau{\unicode{x1D70F}}\) \(\def\upsilon{\unicode{x1D710}}\) \(\def\phi{\unicode{x1D719}}\) \(\def\varphi{\unicode{x1D711}}\) \(\def\chi{\unicode{x1D712}}\) \(\def\psi{\unicode{x1D713}}\) \(\def\omega{\unicode{x1D714}}\) \(\def\upAlpha{\unicode{x0391}}\) \(\def\upBeta{\unicode{x0392}}\) \(\def\upGamma{\unicode{x0393}}\) \(\def\upDigamma{\unicode{x03DC}}\) \(\def\upDelta{\unicode{x0394}}\) \(\def\upEpsilon{\unicode{x0395}}\) \(\def\upZeta{\unicode{x0396}}\) \(\def\upEta{\unicode{x0397}}\) \(\def\upTheta{\unicode{x0398}}\) \(\def\upVartheta{\unicode{x03F4}}\) \(\def\upIota{\unicode{x0399}}\) \(\def\upKappa{\unicode{x039A}}\) \(\def\upLambda{\unicode{x039B}}\) \(\def\upMu{\unicode{x039C}}\) \(\def\upNu{\unicode{x039D}}\) \(\def\upXi{\unicode{x039E}}\) \(\def\upOmicron{\unicode{x039F}}\) \(\def\upPi{\unicode{x03A0}}\) \(\def\upVarpi{\unicode{x03D6}}\) \(\def\upRho{\unicode{x03A1}}\) \(\def\upSigma{\unicode{x03A3}}\) \(\def\upTau{\unicode{x03A4}}\) \(\def\upUpsilon{\unicode{x03A5}}\) \(\def\upPhi{\unicode{x03A6}}\) \(\def\upChi{\unicode{x03A7}}\) \(\def\upPsi{\unicode{x03A8}}\) \(\def\upOmega{\unicode{x03A9}}\) \(\def\itAlpha{\unicode{x1D6E2}}\) \(\def\itBeta{\unicode{x1D6E3}}\) \(\def\itGamma{\unicode{x1D6E4}}\) \(\def\itDigamma{\mathit{\unicode{x03DC}}}\) \(\def\itDelta{\unicode{x1D6E5}}\) \(\def\itEpsilon{\unicode{x1D6E6}}\) \(\def\itZeta{\unicode{x1D6E7}}\) \(\def\itEta{\unicode{x1D6E8}}\) \(\def\itTheta{\unicode{x1D6E9}}\) \(\def\itVartheta{\unicode{x1D6F3}}\) \(\def\itIota{\unicode{x1D6EA}}\) \(\def\itKappa{\unicode{x1D6EB}}\) \(\def\itLambda{\unicode{x1D6EC}}\) \(\def\itMu{\unicode{x1D6ED}}\) \(\def\itNu{\unicode{x1D6EE}}\) \(\def\itXi{\unicode{x1D6EF}}\) \(\def\itOmicron{\unicode{x1D6F0}}\) \(\def\itPi{\unicode{x1D6F1}}\) \(\def\itRho{\unicode{x1D6F2}}\) \(\def\itSigma{\unicode{x1D6F4}}\) \(\def\itTau{\unicode{x1D6F5}}\) \(\def\itUpsilon{\unicode{x1D6F6}}\) \(\def\itPhi{\unicode{x1D6F7}}\) \(\def\itChi{\unicode{x1D6F8}}\) \(\def\itPsi{\unicode{x1D6F9}}\) \(\def\itOmega{\unicode{x1D6FA}}\) \(\def\upalpha{\unicode{x03B1}}\) \(\def\upbeta{\unicode{x03B2}}\) \(\def\upvarbeta{\unicode{x03D0}}\) \(\def\upgamma{\unicode{x03B3}}\) \(\def\updigamma{\unicode{x03DD}}\) \(\def\updelta{\unicode{x03B4}}\) \(\def\upepsilon{\unicode{x03F5}}\) \(\def\upvarepsilon{\unicode{x03B5}}\) \(\def\upzeta{\unicode{x03B6}}\) \(\def\upeta{\unicode{x03B7}}\) \(\def\uptheta{\unicode{x03B8}}\) \(\def\upvartheta{\unicode{x03D1}}\) \(\def\upiota{\unicode{x03B9}}\) \(\def\upkappa{\unicode{x03BA}}\) \(\def\upvarkappa{\unicode{x03F0}}\) \(\def\uplambda{\unicode{x03BB}}\) \(\def\upmu{\unicode{x03BC}}\) \(\def\upnu{\unicode{x03BD}}\) \(\def\upxi{\unicode{x03BE}}\) \(\def\upomicron{\unicode{x03BF}}\) \(\def\uppi{\unicode{x03C0}}\) \(\def\upvarpi{\unicode{x03D6}}\) \(\def\uprho{\unicode{x03C1}}\) \(\def\upvarrho{\unicode{x03F1}}\) \(\def\upsigma{\unicode{x03C3}}\) \(\def\upvarsigma{\unicode{x03C2}}\) \(\def\uptau{\unicode{x03C4}}\) \(\def\upupsilon{\unicode{x03C5}}\) \(\def\upphi{\unicode{x03D5}}\) \(\def\upvarphi{\unicode{x03C6}}\) \(\def\upchi{\unicode{x03C7}}\) \(\def\uppsi{\unicode{x03C8}}\) \(\def\upomega{\unicode{x03C9}}\) \(\def\italpha{\unicode{x1D6FC}}\) \(\def\itbeta{\unicode{x1D6FD}}\) \(\def\itvarbeta{\unicode{x03D0}}\) \(\def\itgamma{\unicode{x1D6FE}}\) \(\def\itdigamma{\mathit{\unicode{x03DD}}}\) \(\def\itdelta{\unicode{x1D6FF}}\) \(\def\itepsilon{\unicode{x1D716}}\) \(\def\itvarepsilon{\unicode{x1D700}}\) \(\def\itzeta{\unicode{x1D701}}\) \(\def\iteta{\unicode{x1D702}}\) \(\def\ittheta{\unicode{x1D703}}\) \(\def\itvartheta{\unicode{x1D717}}\) \(\def\itiota{\unicode{x1D704}}\) \(\def\itkappa{\unicode{x1D705}}\) \(\def\itvarkappa{\unicode{x1D718}}\) \(\def\itlambda{\unicode{x1D706}}\) \(\def\itmu{\unicode{x1D707}}\) \(\def\itnu{\unicode{x1D708}}\) \(\def\itxi{\unicode{x1D709}}\) \(\def\itomicron{\unicode{x1D70A}}\) \(\def\itpi{\unicode{x1D70B}}\) \(\def\itvarpi{\unicode{x1D71B}}\) \(\def\itrho{\unicode{x1D70C}}\) \(\def\itvarrho{\unicode{x1D71A}}\) \(\def\itsigma{\unicode{x1D70E}}\) \(\def\itvarsigma{\unicode{x1D70D}}\) \(\def\ittau{\unicode{x1D70F}}\) \(\def\itupsilon{\unicode{x1D710}}\) \(\def\itphi{\unicode{x1D719}}\) \(\def\itvarphi{\unicode{x1D711}}\) \(\def\itchi{\unicode{x1D712}}\) \(\def\itpsi{\unicode{x1D713}}\) \(\def\itomega{\unicode{x1D714}}\) \(\let \lparen (\) \(\let \rparen )\) \(\newcommand {\cuberoot }[1]{\,{}^3\!\!\sqrt {#1}}\,\) \(\newcommand {\fourthroot }[1]{\,{}^4\!\!\sqrt {#1}}\,\) \(\newcommand {\longdivision }[1]{\mathord {\unicode {x027CC}#1}}\) \(\newcommand {\mathcomma }{,}\) \(\newcommand {\mathcolon }{:}\) \(\newcommand {\mathsemicolon }{;}\) \(\newcommand {\overbracket }[1]{\mathinner {\overline {\ulcorner {#1}\urcorner }}}\) \(\newcommand {\underbracket }[1]{\mathinner {\underline {\llcorner {#1}\lrcorner }}}\) \(\newcommand {\overbar }[1]{\mathord {#1\unicode {x00305}}}\) \(\newcommand {\ovhook }[1]{\mathord {#1\unicode {x00309}}}\) \(\newcommand {\ocirc }[1]{\mathord {#1\unicode {x0030A}}}\) \(\newcommand {\candra }[1]{\mathord {#1\unicode {x00310}}}\) \(\newcommand {\oturnedcomma }[1]{\mathord {#1\unicode {x00312}}}\) \(\newcommand {\ocommatopright }[1]{\mathord {#1\unicode {x00315}}}\) \(\newcommand {\droang }[1]{\mathord {#1\unicode {x0031A}}}\) \(\newcommand {\leftharpoonaccent }[1]{\mathord {#1\unicode {x020D0}}}\) \(\newcommand {\rightharpoonaccent }[1]{\mathord {#1\unicode {x020D1}}}\) \(\newcommand {\vertoverlay }[1]{\mathord {#1\unicode {x020D2}}}\) \(\newcommand {\leftarrowaccent }[1]{\mathord {#1\unicode {x020D0}}}\) \(\newcommand {\annuity }[1]{\mathord {#1\unicode {x020E7}}}\) \(\newcommand {\widebridgeabove }[1]{\mathord {#1\unicode {x020E9}}}\) \(\newcommand {\asteraccent }[1]{\mathord {#1\unicode {x020F0}}}\) \(\newcommand {\threeunderdot }[1]{\mathord {#1\unicode {x020E8}}}\) \(\newcommand {\Bbbsum }{\mathop {\unicode {x2140}}\limits }\) \(\newcommand {\oiint }{\mathop {\unicode {x222F}}\limits }\) \(\newcommand {\oiiint }{\mathop {\unicode {x2230}}\limits }\) \(\newcommand {\intclockwise }{\mathop {\unicode {x2231}}\limits }\) \(\newcommand {\ointclockwise }{\mathop {\unicode {x2232}}\limits }\) \(\newcommand {\ointctrclockwise }{\mathop {\unicode {x2233}}\limits }\) \(\newcommand {\varointclockwise }{\mathop {\unicode {x2232}}\limits }\) \(\newcommand {\leftouterjoin }{\mathop {\unicode {x27D5}}\limits }\) \(\newcommand {\rightouterjoin }{\mathop {\unicode {x27D6}}\limits }\) \(\newcommand {\fullouterjoin }{\mathop {\unicode {x27D7}}\limits }\) \(\newcommand {\bigbot }{\mathop {\unicode {x27D8}}\limits }\) \(\newcommand {\bigtop }{\mathop {\unicode {x27D9}}\limits }\) \(\newcommand {\xsol }{\mathop {\unicode {x29F8}}\limits }\) \(\newcommand {\xbsol }{\mathop {\unicode {x29F9}}\limits }\) \(\newcommand {\bigcupdot }{\mathop {\unicode {x2A03}}\limits }\) \(\newcommand {\bigsqcap }{\mathop {\unicode {x2A05}}\limits }\) \(\newcommand {\conjquant }{\mathop {\unicode {x2A07}}\limits }\) \(\newcommand {\disjquant }{\mathop {\unicode {x2A08}}\limits }\) \(\newcommand {\bigtimes }{\mathop {\unicode {x2A09}}\limits }\) \(\newcommand {\modtwosum }{\mathop {\unicode {x2A0A}}\limits }\) \(\newcommand {\sumint }{\mathop {\unicode {x2A0B}}\limits }\) \(\newcommand {\intbar }{\mathop {\unicode {x2A0D}}\limits }\) \(\newcommand {\intBar }{\mathop {\unicode {x2A0E}}\limits }\) \(\newcommand {\fint }{\mathop {\unicode {x2A0F}}\limits }\) \(\newcommand {\cirfnint }{\mathop {\unicode {x2A10}}\limits }\) \(\newcommand {\awint }{\mathop {\unicode {x2A11}}\limits }\) \(\newcommand {\rppolint }{\mathop {\unicode {x2A12}}\limits }\) \(\newcommand {\scpolint }{\mathop {\unicode {x2A13}}\limits }\) \(\newcommand {\npolint }{\mathop {\unicode {x2A14}}\limits }\) \(\newcommand {\pointint }{\mathop {\unicode {x2A15}}\limits }\) \(\newcommand {\sqint }{\mathop {\unicode {x2A16}}\limits }\) \(\newcommand {\intlarhk }{\mathop {\unicode {x2A17}}\limits }\) \(\newcommand {\intx }{\mathop {\unicode {x2A18}}\limits }\) \(\newcommand {\intcap }{\mathop {\unicode {x2A19}}\limits }\) \(\newcommand {\intcup }{\mathop {\unicode {x2A1A}}\limits }\) \(\newcommand {\upint }{\mathop {\unicode {x2A1B}}\limits }\) \(\newcommand {\lowint }{\mathop {\unicode {x2A1C}}\limits }\) \(\newcommand {\bigtriangleleft }{\mathop {\unicode {x2A1E}}\limits }\) \(\newcommand {\zcmp }{\mathop {\unicode {x2A1F}}\limits }\) \(\newcommand {\zpipe }{\mathop {\unicode {x2A20}}\limits }\) \(\newcommand {\zproject }{\mathop {\unicode {x2A21}}\limits }\) \(\newcommand {\biginterleave }{\mathop {\unicode {x2AFC}}\limits }\) \(\newcommand {\bigtalloblong }{\mathop {\unicode {x2AFF}}\limits }\) \(\newcommand {\arabicmaj }{\mathop {\unicode {x1EEF0}}\limits }\) \(\newcommand {\arabichad }{\mathop {\unicode {x1EEF1}}\limits }\)

10.5 Compressing LZ77 Tokens

The LZSS encoding we discussed in the previous section encodes each Byte and Match token using a fixed number of bits. In practice, however, some tokens occur much more frequently than others. Wouldn’t it therefore be preferable to encode common tokens using fewer bits than rare tokens? The answer is yes, but designing such an encoding poses a few interesting and unexpected challenges.

A straightforward solution would be to count how often each token occurs and then use Huffman’s algorithm to design an optimal bit encoding. At first sight, this works well. For example, if we split the full text of Alice in Wonderland into tokens and construct the corresponding Huffman code, we can encode each token using an average of 13.3 bits, whereas LZSS requires 17.2 bits on average. There is a catch, however: The 13.3 bits for Huffman coding do not include the size of the Huffman tree, which must be transmitted along with the compressed data. Unfortunately, this tree is massive since there are more than 20 000 different tokens. If we include the size of the encoded tree in the calculation, the storage requirements more than doubles to 27.3 bits per token, which is almost 60 % more than the simple LZSS encoding.

It’s easy to see why Huffman coding doesn’t work well in this case by considering the frequency distribution of tokens shown in Table 10.1. There are 74 unique Byte tokens, the majority of which (61 or 82 %) occurs at least ten times; this is the kind of distribution for which Huffman coding works well. In contrast, most of the 20 027 Match tokens occur just once, so the gains made by Huffman coding are more than offset by the need to include a Huffman tree with more than 20 000 nodes in the output. There is simply too little redundancy in the Match tokens to justify the overhead of Huffman coding.

Table 10.1 Token distribution for Alice in Wonderland.
.
Frequency Match tokens Byte tokens
\(1\times \) 16830 7
\(2\times \) 2340 2
\(3\times \) 544
\(4\times \) 186 1
\(5\times \) 70 2
\(6\times \) 30
\(7\times \) 14
\(8\times \) 5 1
\(9\times \) 4
\(\ge 10\times \) 4 61
Total 20027 74

But there are still ways to improve on the fixed-length encoding used by LZSS. As a first step, let’s take a closer look at the Match tokens that occur when compressing Alice in Wonderland. Figure 10.4 shows a histogram of all the offset values. It’s obvious that small offset values occur much more frequently than large values, which means that in most cases the best match is found in the preceding few thousand bytes. The histogram of the length values looks similar, which means that most matches are close by and fairly short. We therefore need a way to encode integers like offset and length so that small values take up fewer bits in the output than large values.

Figure 10.4 Frequency of different match offsets when compressing Alice in Wonderland
Variable-Length Codes for Integers

There are many ways to encode integers using a variable number of bits. We already encountered two examples in the previous chapter: The unary code discussed in Exercise 9.6 encodes a positive integer \(n\) as a sequence of \(n\) ones followed by a single zero, and the UTF-8 encoding discussed in Exercise 9.14 stores numbers between 0 and 0x10FFFF using one to four bytes. Another class of such variable-length integers can be obtained by encoding an integer’s binary representation and the length of this representation separately.

Let’s start with a simple example of such an encoding. Given a nonnegative integer \(n\), we first encode its bit length \(\bitlen (n)\) using a fixed number of bits \(L\) and then append the \(\bitlen (n)-1\) least significant bits of the binary representation of \(n\) itself. (For \(n=0\) and \(n=1\), we append no additional bits.) Table 10.2 demonstrates this encoding for a few small values of \(n\), in this case using \(L=5\) bits to encode \(\bitlen (n)\). The first two columns show the number being encoded in decimal and binary form, the next two columns its bit length, and the final column the resulting bit sequence, which is obtained by extending \(\bitlen (n)\) to 5 bits and then appending the lowest \(\bitlen (n)-1\) bits of \(n\).

Table 10.2 Variable-length encoding of integers.
.
\(n\) \(\bitlen (n)\) Codeword
\(0\) \(0_2\) \(0\) \(0_2\) 00000
\(1\) \(1_2\) \(1\) \(1_2\) 00001
\(2\) \(10_2\) \(2\) \(10_2\) 00010 0
\(3\) \(11_2\) \(2\) \(10_2\) 00010 1
\(4\) \(100_2\) \(3\) \(11_2\) 00011 00
\(5\) \(101_2\) \(3\) \(11_2\) 00011 01
\(6\) \(110_2\) \(3\) \(11_2\) 00011 10
\(7\) \(111_2\) \(3\) \(11_2\) 00011 11
\(8\) \(1000_2\) \(4\) \(100_2\) 00100 000
\(\vdots \) \(\vdots \) \(\vdots \) \(\vdots \) \(\vdots \)
\(3410\) \(110101010010_2\) 12 \(1100_2\) 01100 10101010010
\(\vdots \) \(\vdots \) \(\vdots \) \(\vdots \) \(\vdots \)

The range of numbers that can be encoded using this format depends on the number \(L\) of bits used to encode \(\bitlen (n)\). The largest positive number that can be encoded is

\[ n_{\max } = 2^{2^{L}-1}-1. \]

Conversely, if \(n_{\max }\) is the largest number of interest, we need at least

\[ L=\bitlen \bigl (\bitlen (n_{\max })\bigr ) \]

bits to encode the bit length of \(n_{\max }\). For \(L=5\) the largest value that can be encoded is \(n_{\max }=2^{31}-1\), which is also the largest number that can be stored in a signed 32-bit integer.

The following method demonstrates how to encode an integer n in this format. The resulting bit sequence is written to out and the parameter lengthBits indicates the number of bits that are used for \(\bitlen (n)\).

public static void writeInteger(
        int n, int lengthBits, BitOutputStream out)
        throws IOException {
    int length = IntUtil.bitLength(n);
    out.writeBits(length, lengthBits);
    if (length > 1)
        out.writeBits(n, length - 1);
}

Decoding an integer stored in this format is just as easy. We first read \(L\) bits to recover \(\bitlen (n)\). If \(\bitlen (n)\le 1\), we are done and return \(\bitlen (n)=n\). Otherwise, we read \(\bitlen (n)-1\) additional bits for the least significant bits of \(n\) and finish by adding the missing most significant bit:

static int readInteger(BitInputStream in, int lengthBits)
        throws IOException {
    int length = (int) in.readBits(lengthBits);
    if (length <= 1)
        return length;
    return (1 << length - 1) | (int) in.readBits(length - 1);
}

The main limitation of the code we just described is that it cannot encode integers using fewer than \(L\) bits, which can be a problem if small numbers are overwhelmingly common. We can easily remedy this problem by choosing a different code for \(\bitlen (n)\). For instance, given an arbitrary prefix code bitlenCode, we can generalize the implementation of writeInteger() as follows:

static void writeInteger(
        int n, PrefixCode bitlenCode, BitOutputStream out) {
    int length = IntUtil.bitLength(n);
    bitlenCode.writeSymbol(length, out);
    if (length > 1)
        out.writeBits(n, length - 1);
}

To read an integer stored in this format, we first use bitlenCode to decode the bit length and then read the remaining bits as before.

The LZH Encoding

We can now turn to the problem of designing a more efficient encoding for LZ77 tokens. Here is our general plan of attack:

  • For Byte tokens, we will use a Huffman code because typically some bytes occur much more frequently than others.

  • For Match tokens, we will encode the offset and length fields as variable-length integers, similar to the ones we just described. We will use Huffman codes to encode the bit lengths of the two fields.

The use of Huffman coding requires that the compression algorithm processes the input in two passes: The first pass converts the input into LZ77 tokens and constructs the necessary Huffman codes, and the second pass converts the tokens into bit sequences and writes them to the output.

The simplest implementation of the plan outlined above requires three Huffman codes: one for the Byte tokens and one each for the \(\bitlen (\Id {offset})\) and \(\bitlen (\Id {length})\) fields of Match tokens. In practice, it is more efficient to use one combined Huffman code for Byte tokens and length fields and a separate Huffman code for the offset fields. Listing 10.16 shows the outline of LZHEncoder, the class that will house the resulting compression algorithm. We will call the resulting format LZH because it is a combination of Lempel-Ziv compression and Huffman coding. The two Huffman codes we will use are stored in offsetCode and tagCode. As before, maxOffset, maxLength, and minLength are three parameters that control the conversion of the input into tokens. Because values of length below minLength are never used, we will encode the reduced length

\[ \Id {length}' = \Id {length} - \mathtt {minLength} \]

instead.

Listing 10.16ch10 / LZHEncoder

// A class for compressing data using LZ77 and Huffman coding.
public class LZHEncoder {
    // Prefix codes for encoding tokens.
    PrefixCode offsetCode;
    PrefixCode tagCode;

    // Parameters for LZ77.
    final int maxOffset;
    final int minLength;
    final int maxLength;

    // ...
}

The first code offsetCode is the simpler of the two and encodes the bit lengths of the offset fields in Match tokens. In contrast, the second code tagCode is more complicated because it can encode three different kinds of information, depending on the context:

  • The first 256 symbols represent all possible Byte tokens.

  • The next symbol with value 256 is a special tag that marks the end of the compressed data.

  • The remaining tags starting at 257 represent Match tokens and store the bit length of \(\Id {length}'\),

The meaning of all possible tags is summarized in Fig. 10.5.

.
Tag Symbol
Byte(0) 0

} Byte tokens

Byte(1) 1
Byte(255) 255
end of file 256
\(\bitlen (\Id {length}') = 0\) 257

} Match tokens

\(\bitlen (\Id {length}') = 1\) 258
Figure 10.5 Information encoded using tagCode. The code covers all possible Byte tokens, the end of file marker, and some information associated with Match tokens.

Listing 10.17 demonstrates how tagCode and offsetCode are computed. We first prepare two frequency tables, tagCount and offsetCount, which are used to tally the frequency of the different tags and \(\bitlen (\Id {offset})\) values, respectively. For each Byte token in the input, we increment the corresponding entry in tagCount, and for each Match token, we increment the entry for \(\bitlen (\Id {length}')\) in tagCount and the one for \(\bitlen (\Id {offset})\) in offsetCount. Once this is done, we use the resulting frequency tables to construct the two Huffman codes.

Listing 10.17ch10 / LZHEncoder

// Analyze LZ77 tokens and prepare Huffman codes.
void prepareCodes(long[] tokens, int numTokens) {
    int[] tagCount =
            new int[257 + bitLength(maxLength - minLength) + 1];
    int[] offsetCount = new int[bitLength(maxOffset) + 1];
    tagCount[256] = 1;  // end of file occurs exactly once
    for (int i = 0; i < numTokens; i++) {
        long token = tokens[i];
        if (LZToken.isByteToken(token)) {
            tagCount[LZToken.getByte(token)] += 1;
        } else {
            int length = LZToken.getLength(token) - minLength;
            int offset = LZToken.getOffset(token);
            tagCount[257 + bitLength(length)] += 1;
            offsetCount[bitLength(offset)] += 1;
        }
    }
    tagCode = HuffmanCode.construct(tagCount);
    offsetCode = HuffmanCode.construct(offsetCount);
}

Using these two Huffman codes we can now encode a single LZ77 token as follows. If the token is a Byte token, we use tagCode to output the symbol with same numeric value. If the token is a Match token, we output four pieces of data, as shown in Listing 10.18:

  • 1. The value \(257+\bitlen (\Id {length}')\), encoded using tagCode.

  • 2. The \(\bitlen (\Id {length}')-1\) least significant bits of \(\Id {length}'\).

  • 3. The value \(\bitlen (\Id {offset})\), encoded using offsetCode.

  • 4. The \(\bitlen (\Id {offset})-1\) least significant bits of offset.

As you can see, values encoded using tagCode serve two purposes: They announce the type of token that follows in the stream and, for added efficiency, include some information about that token as well.

Listing 10.18ch10 / LZHEncoder

// Encode a single token and write the resulting bits to output.
private void writeToken(BitOutputStream output, long token)
        throws IOException {
    if (LZToken.isByteToken(token)) {
        tagCode.writeSymbol(LZToken.getByte(token), output);
    } else {
        int length = LZToken.getLength(token) - minLength;
        int offset = LZToken.getOffset(token);
        tagCode.writeSymbol(257 + bitLength(length), output);
        if (length > 1)
            output.writeBits(length, bitLength(length) - 1);
        offsetCode.writeSymbol(bitLength(offset), output);
        if (offset > 1)
            output.writeBits(offset, bitLength(offset) - 1);
    }
}

The general process for compressing a file using LZH is similar to the one for LZSS compression: We convert the file into a list of tokens and then encode each token as a sequence of bit. The main difference is that LZH compression requires an additional pass over the list of tokens to analyze their distribution and construct the necessary Huffman codes. This poses a new problem: When compressing large files, it may not be possible to store all tokens in memory, and scanning the entire input twice is costly and not always possible. Most compression programs therefore split large files into smaller blocks that fit into memory and can then be compressed independently.

The compress() method in Listing 10.19 compresses a given input by splitting it into blocks of a \(2^{20}\) tokens. For each block, we initialize tagCode and offsetCode by calling prepareCodes(). We then write the codes themselves to the output, and finally encode each token using writeToken(). After the last block has been processed, we end the output stream with a single end of file tag.

Listing 10.19ch10 / LZHEncoder

public void compress(InputStream input, BitOutputStream output)
        throws IOException {
    final int blockSize = 1024 * 1024;
    var tokens = new long[blockSize];
    var lz = new LZTokenizer(input, maxOffset, maxLength, minLength);
    output.writeBits(maxOffset, 32);
    output.writeBits(blockSize, 32);
    while (lz.hasRemaining()) {
        int numTokens = lz.readTokens(tokens);
        prepareCodes(tokens, numTokens);
        tagCode.writeCode(output);
        offsetCode.writeCode(output);
        for (int i = 0; i < numTokens; i++)
            writeToken(output, tokens[i]);
    }
    tagCode.writeSymbol(256, output);  // end marker
    output.flush();
}

The final member of LZHEncoder we need to implement is the constructor, which initializes the parameters maxOffset, minLength, and maxLength. We again prevent short matches by setting minLength to 3, but the improved encoding of Match tokens gives us considerably more latitude when it comes to setting the other two parameters. For instance, we can afford to increase maxLength to 1024, which is a more than a hundred times larger than the value we used for LZSS.

The most important parameter is maxOffset, because it determines the size of the dictionary region. Instead of using a fixed value, we let users of LZHEncoder control maxOffset indirectly by specifying the desired compression quality as a non-negative integer quality. We set

\[ \mathtt {maxOffset} = 2^{10 + \Id {quality}}, \]

so the dictionary doubles in size every time we increment quality. At the lowest quality 0, the dictionary is just 1024 bytes in size, and the largest quality we accept is 15, which corresponds to a dictionary of 32 megabytes (Listing 10.20).

Listing 10.20ch10 / LZHEncoder

public LZHEncoder(int quality) {
    if (quality < 0 || quality > 15)
        throw new IllegalArgumentException();
    maxOffset = (1 << (10 + quality)) - 1;
    minLength = 3;
    maxLength = 1024;
}

In practice, each increment of quality reduces the size of the compressed output by a few percentage points. At the same time, however, each increment also increases the running time by approximately 30 % to 100 %, mainly because a larger dictionary must be searched.

The decompression algorithm for LZH is discussed in Exercise 10.4.

Exercises

Exercise 10.3. The Elias gamma code is a variable-length code for integers that is similar to the codes discussed in this section. For a given number \(n\), it encodes the number’s bit length and the actual bits as follows:

  • 1. Write a sequence of \(\bitlen (n)-1\) zero-bits followed by a single one-bit.2

  • 2. Append the \(\bitlen (n)-1\) least significant bits of \(n\).

Use this code to encode the numbers in Table 10.2.

Exercise 10.4. Complete the implementation of LZH compression in Section 10.5 by writing a program that takes the output of LZHEncoder and decodes it, recovering the original uncompressed file.

2 This is a so-called unary code, which we already discussed in Exercise 9.6.