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.2 From Bytes to Tokens

We will now turn to the problem of implementing the basic algorithms for LZ77 discussed in the previous section. As in our discussion of Huffman coding in the previous chapter, our final goal is to write a small compression program that takes a stream of bytes and outputs a compressed sequence of bits. In the case of LZ77, this problem can be divided into two steps:

  • 1. Convert the byte sequence into a sequence of LZ77 tokens. The main challenge in this step is to quickly find potential matches in the dictionary.

  • 2. Convert the tokens into an efficient bit sequence. We will discuss two possible encodings for tokens: a simple one called LZSS that maps tokens to bit patterns of fixed length, and a more complex one that adds a second layer of compression by applying Huffman coding to the tokens themselves.

In this section and the next we will discuss the first step, how to convert the input into a sequence of tokens. In Sections 10.4 and 10.5 we will explain methods for encoding tokens.

The first step of LZ77 compression is to analyze the input and turn it into a sequence of Byte andMatch tokens. Algorithm 10.1 made this look easy: iterate over the input, find the longest match in the dictionary using two nested for loops, and output the resulting tokens one by one. Real-world implementations of LZ77 are significantly more complex, however, because they need perform well with large dictionaries and large files.

The performance concerns start with the representation of tokens. In Java, the natural way to represent Byte and Match tokens would be to define two records Byte and Match that implement a common interface SimpleToken:

// Data type for representing LZ77 tokens.
public sealed interface SimpleToken {
    record Byte(byte value) implements SimpleToken {
    }

    record Match(int offset, int length) implements SimpleToken {
    }
}

We used these data types to implement LZ77 in Exercise 10.2.

However, when working with large files and millions of tokens, we can save time and memory by packing both kinds of tokens inside the 64 bits of a single long integer: For Byte tokens, we set the upper 56 bits to zero and store the value in the least significant 8 bits, and for Match tokens, we use the upper 32 bits to store the offset field and the lower 32 bits to store the length field:

(-tikz- diagram)

We can easily distinguish the two kinds of tokens in this representation: The offset field is a positive integer, so the upper 56 bits are zero for Byte and nonzero for Match tokens.

With this convention, creating Byte tokens and retrieving their values can be accomplished as shown in Listing 10.2. In makeByte() function converts single byte into a token by converting it to long and then clearing the upper 56 bits. The isByteToken() function checks whether a given long represents a Byte token by checking whether the upper 56 bits are all zero. Listing 10.3 defines similar functions for working with Match tokens. The makeMatch() function combines two 32-bit integers into a single long, and getLength() and getOffset() extracts the the original values.

Listing 10.2ch10 / LZToken

// Create BYTE token for 'value'.
public static long makeByte(byte value) {
    return (long) value & 0xffL;
}

// Return value of BYTE token as number between 0 and 255.
public static int getByte(long token) {
    return (int) token;
}

// Is token a BYTE?
public static boolean isByteToken(long token) {
    return (token & ~0xffL) == 0;
}

Listing 10.3ch10 / LZToken

// Create MATCH token.
public static long makeMatch(int offset, int length) {
    return ((long) offset << 32) | (long) length;
}

// Retrieve length and offset fields of a MATCH token.
public static int getLength(long token) {
    return (int) token;
}
public static int getOffset(long token) {
    return (int) (token >>> 32);
}

How much memory does this representation save in practice? On a typical 64-bit computer, the Byte and Match records defined in Listing 10.1 consume 16 bytes and 24 bytes of memory, respectively, compared to the 8 bytes per token we achieve by storing them in a long integer; we therefore reduce memory consumption by between 50 % and 67 %.

It turns out that this new representation is also faster because, on most computers, processing arrays of longs is more efficient than processing arrays of SimpleTokens. One reason is that reading data from memory is a relatively slow operation on modern CPUs, so reducing the footprint of a single token is already a win. In addition, processing contiguous blocks of memory (which Java uses for arrays of primitive types such as integer or long) is faster than processing objects that are scattered across the computer’s memory (which often happens to objects like SimpleToken). For more information on the impact of CPUs and their memory architecture on program performance, see the book by Bryant and O’Hallaron [22, Chapter 6].

Cyclic Buffers

We mentioned earlier that all implementations of LZ77 restrict the range of possible Match tokens by placing limits on its offset and length fields:

\begin{align*} 1 &\le \Id {offset} \le \Const {MaxOffset}\\ \Const {MinLength} &\le \Id {length} \le \Const {MaxLength} \end{align*} As a result, only bytes in a small region around the current input position are accessed by the algorithm: The last MaxOffset bytes to the left of the current position serve as the dictionary and the following MaxLength bytes to the right are accessed when searching for the longest match. We therefore only need to keep a small region consisting of \(\textsf {MaxOffset} + \textsf {MaxLength}\) bytes of the input in memory at any time, which is especially important when dealing with large files.

To map the currently visible portion of the file to an array of bytes in memory we use a clever data structure known as a cyclic buffer. A cyclic buffer is an array of size \(N\) that wraps around at both ends: The last element buffer[N-1] is followed by the first element buffer[0] and buffer[0] is preceded by buffer[N-1]. As we iterate over the input, we place the bytes we read into successive entries of the buffer, the first byte into buffer[0], the second into buffer[1], and so on. Every time we reach the end of the buffer we wrap around to its start, so the byte at position \(N\) in the file overwrites the entry in buffer[0], the one at position \(N+1\) the entry in buffer[1], and so on.

Figure 10.3 illustrates how we can use such a cyclic buffer to store the currently visible part of the input when implementing LZ77. We assume that \(\mathsf {MaxOffset}=6\) and \(\mathsf {MaxLength}=4\), so we use a buffer of size 10. The first diagram shows the contents of the buffer after the first six bytes of the input have been processed: The left half of the buffer stores the dictionary region and the right half the part of the input that must be compressed next; the current position is 6. In the second diagram, the input has advanced by one byte, which moves the current position to 7, shifts the dictionary region one place to the right, and stores the next byte read from the input in the buffer’s leftmost entry. The final diagram shows the contents of the buffer after advancing by four more bytes; now the dictionary region wraps around to the start of the buffer.

Figure 10.3 

A cyclic buffer is a fixed-size array that wraps around at both ends. Here, we use an array of length 10 to be able to access the next 4 bytes of the input and up to 6 bytes previous bytes.

To map file positions to buffer indexes we use the modulo operator: If \(k\) is the current position in the file, \(k\bmod N\) gives us the index of the current byte, \((k-1)\bmod N\) the index of the previous byte, and so on. Since \(k\) is nonnegative, we can use the remainder operator % to implement this mapping:

buffer[k % buffer.length];

Since computing the remainder is fairly expensive, a common trick is to make the size of the buffer \(N\) a power of 2, which allows us to compute the remainder \(k\bmod N\) by extracting the least significant bits of \(k\) using the expression \(k\AND (N-1)\); accessing an entry in the buffer can then be written as

buffer[k & (buffer.length - 1)];

This may look like a mild case of premature optimization, but writing to and reading from the buffer are among the most common operations performed by our implementation of LZ77. Especially when compressing large files, this simple optimization saves millions of remainder operations, which does have a measurable performance impact.

We delegate the details of creating and managing cyclic buffers to a separate class called CyclicBuffer whose only member is an array of bytes buffer (Listing 10.4). The constructor of CyclicBuffer takes the desired size of the buffer as its only argument. To be able to implement the buffer’s wrap-around logic using bit operations, this size is rounded to the next larger power of 2 before allocating the array for buffer. The two methods get() and set() can now perform their index computations using bitwise AND instead of modulo operations.

Listing 10.4ch10 / CyclicBuffer

// A fixed-size cyclic buffer of bytes.
public class CyclicBuffer {
    private final byte[] buffer;

    public CyclicBuffer(int size) {
        // Round the buffer size to next larger power of 2.
        buffer = new byte[1 << IntMath.ceilBinaryLog(size)];
    }

    public byte get(int index) {
        return buffer[index & (buffer.length - 1)];
    }

    public void set(int index, byte v) {
        buffer[index & (buffer.length - 1)] = v;
    }
}

IntMath: ceilBinaryLog()

For a given number \(n\), the nearest power of 2 that is at least as large as \(n\) is \(2^{\lceil \log _2 n\rceil }\): we first take the binary logarithm of \(n\), round it up to the next larger integer, and then turn the result into a power of 2. We already met the expression \(\lceil \log _2 n\rceil \) in Section 5.3 and also implemented a function ceilBinaryLog() that computes it. Combined with a left shift to compute a power of 2, this gives us the following expression, which is used to initialize the buffer used in CyclicBuffer():

1 << IntMath.ceilBinaryLog(size)
Producing the Next Token

We collect the code for reading the input and generating tokens in a class called LZTokenizer, which is outlined in Listing 10.5. The InputStream being compressed is stored in input, and the parameters of the LZ77 algorithm in maxOffset, maxLength, and minLength. The constructor LZTokenizer() first initializes the four fields defined above and then calls initBuffers() and initIndex() to prepare a few additional data structures.

Listing 10.5ch10 / LZTokenizer

// Converts a stream of bytes into a sequence of LZ77 tokens.
public class LZTokenizer {
    private final InputStream input;
    private final int maxOffset;
    private final int maxLength;
    private final int minLength;

    public LZTokenizer(InputStream input, int maxOffset, int maxLength,
            int minLength) throws IOException {
        this.input = input;
        this.maxOffset = maxOffset;
        this.maxLength = maxLength;
        this.minLength = minLength;
        initBuffers();
        initIndex();
    }

    // ...
}

We use one cyclic buffer to store the bytes from the input that are currently visible; this is the “window” through which we view the file, so we call the corresponding field window. We manage this buffer using two integers front and lookaheadSize: front is the current position in the file and points to the next uncompressed byte, and lookaheadSize is the current number of bytes in the lookahead region. Except at the end of the file, lookaheadSize is always equal to maxLength.

Listing 10.6ch10 / LZTokenizer

private CyclicBuffer window;  // the currently visible part of the input
private int front;  // the current position
private int lookaheadSize;  // number of bytes in window after 'front'

public boolean hasRemaining() {
    return lookaheadSize != 0;
}

private void initBuffers() throws IOException {
    this.window = new CyclicBuffer(maxOffset + maxLength);
    this.front = 0;
    this.lookaheadSize = 0;
    while (lookaheadSize < maxLength) {
        int nextByte = input.read();
        if (nextByte == -1)
            break;
        window.set(lookaheadSize++, (byte) nextByte);
    }
}

The input window is initialized in initBuffers(). First, we allocate a cyclic buffer that is large enough to hold the dictionary region and the lookahead region, so its size must be at least maxOffset + maxLength. We then fill the lookahead region with the first maxLength bytes from the file, writing each byte to window and incrementing lookaheadSize afterward.

The main method in class LZTokenizer is nextToken(), which computes and returns the next token (Listing 10.7). The function first searches the dictionary to find the longest match for the byte sequence in the lookahead region. We use a method called findMatches() to compute a list of positions where a match might be found and then compare each of these positions to the bytes at front. The length and offset of the longest match found so far are stored in bestLength and bestOffset. If the best match is sufficiently long, the function returns a Match token and advances the input by bestLength bytes; otherwise, it returns a Byte token and advances by a single byte.

Listing 10.7ch10 / LZTokenizer

// Compute a token for one or more bytes at the current position.
public long nextToken() throws IOException {
    // Find the longest match
    int bestLength = 0, bestOffset = 0;
    for (int matchStart : findMatches()) {
        int n;  // length of current match
        for (n = 0; n < lookaheadSize; n++) {
            if (window.get(front + n) != window.get(matchStart + n))
                break;
        }
        if (n > bestLength) {
            bestLength = n;
            bestOffset = front - matchStart;
        }
    }
    // Return either MATCH or BYTE token
    if (bestLength >= minLength) {
        advanceInput(bestLength);
        return LZToken.makeMatch(bestOffset, bestLength);
    } else {
        advanceInput(1);
        return LZToken.makeByte(window.get(front - 1));
    }
}

Because we will later want to process tokens in large batches instead of individually, we also provide a method readTokens() that reads as many tokens as possible and stores them in the given array of long integers (Listing 10.8). The function returns the number of tokens that was read; this is less than size of the array if the end of the input was encountered.

Listing 10.8ch10 / LZTokenizer

// Compute multiple tokens. Returns the number of tokens generated.
public int readTokens(long[] tokens) throws IOException {
    int len = 0;
    while (hasRemaining() && len < tokens.length)
        tokens[len++] = nextToken();
    return len;
}