Structures

The last array in the apparatus

The document-listing apparatus began as three arrays beside a suffix array. Two of them turned out to be machinery for reading the third, and both have gone. What is left is the document array, and it is the only one of the three that was information.

Three arrays were needed to answer the question which documents contain this pattern at a cost proportional to the answer rather than to the occurrences.

D, the document array: for each row of the suffix array, which document that row’s suffix belongs to. C, the previous-occurrence chain: for each row, the previous row holding the same document, or minus one. And a range minimum over C, so that the smallest chain value in a range can be found without scanning it.

Muthukrishnan’s observation is that inside the rows for a pattern, the first row belonging to each document is exactly the row whose chain entry falls outside the range. So the distinct documents are the positions where C is less than the range’s start, and finding all of them is a range minimum, a report and two recursions.

Two of those three arrays have since gone, and the two that went were the two that were machinery.

Three apparatus for one question, and what each of them holdsThe bits each listing method needs beyond the suffix array and the text, on 64 documents of 256 characters. The published method holds the document array, a previous-occurrence chain of the same width as the suffix array, and a range minimum over that chain: 838,797 bits. Removing the chain — because the test it exists for is answerable from the answer so far — leaves 285,460. Putting the document array in a wavelet tree removes the range minimum too, because the distinct symbols of an interval are what the tree enumerates: 119,972, or 14.3% of where the strand started. What is left is the array itself, compressed, and one descent over it.range minimum and chain838,797255.0%range minimum, no chain285,46086.8%one descent over D119,97236.5%the document array, the chain and a range minimumthe document array, a range minimum and one bit per documentthe document array, in a wavelet tree64 documents · 16,447 characters14.3% of the published apparatus
Fig. 1 The bits each listing method needs beyond the suffix array and the text, on sixty-four documents of two hundred and fifty-six characters.

What went, and why each one could

The chain went because the answer knew. The array the walk never reads is Sadakane’s substitution: the test C[at] < lo asks is this the first row of this document in the range, and a walk that visits the left subrange before the right has already reported every document whose first row lies to the left. So the answer-so-far can answer the question the array existed for, and the array — n⌈log₂ n⌉ bits, exactly the width of the suffix array — goes, replaced by one bit per document.

The replacement is not free and the essay measuring it says so: the bitmap has to be cleared between queries, and clearing it by walking it costs d per query, which on two thousand documents answering a query whose answer is three is worse than the array was. A document already in the answer is where that trap is written out.

The range minimum shrank because it did not need values. The apparatus that is smaller than its index replaced a segment tree with a succinct structure — a Cartesian tree in 2n + o(n) bits — and gained a factor of 3.19 on that part alone. It did not go; it got smaller.

D stayed, because nothing else in the collection knows which document a row belongs to. It is the only one of the three whose contents cannot be derived from anything else present.

Machinery against information

That distinction is the reason this essay exists rather than being a summary.

C is computable from D in one pass: walk the rows in order, remember the last row seen for each document, write it down. So C holds no information D does not; it is a precomputed answer to a question about D, kept because answering that question at query time was thought to be expensive. It turned out not to be, and the array went.

The range minimum holds no information at all. It is an index over C, and C is an index over D. Two levels of machinery over one array.

D is different. Given the text and the suffix array, D requires knowing where the documents begin — and that is an input, supplied by whoever built the collection. It is not derivable, it is not compressible to nothing, and its size is a real quantity: n⌈log₂ d⌉ bits, which on sixty-four documents over sixteen thousand characters is ninety-eight thousand bits, or twelve kilobytes for a two-kilobyte collection.

Four methods, one query, the same documentsThe pattern " until" on a collection of 24 documents: 54 occurrences spread over 8 documents. Reading every occurrence costs the occurrences — 54 — and is proportional to the range rather than to the answer. The published range-minimum walk costs 267 operations inside the tree, which is the log the method's own account leaves in the constant. Removing the chain brings it to 83. One descent over the document array costs 32. All four report the same 8 documents, and the check is on the documents and not on their number, because the descent produces them in a different order and a comparison of counts would hide a shape mistake.every occurrence548 documentsrange minimum and chain2678 documentsrange minimum, no chain838 documentsone descent over D328 documentsthe document arraythe document array, the chain and a range minimumthe document array, a range minimum and one bit per documentthe document array, in a wavelet tree54 occurrences in 8 documentsoperations inside the structure
Fig. 2 The four methods on one query, with what each of them holds. They differ by which arrays exist rather than by how the arrays are read.

There is a third category worth naming because the bitmap that replaced the chain falls into it: state. One bit per document, cleared between queries, holding nothing between them. It is neither information about the collection nor machinery derived from it; it is scratch space whose size is a function of the collection and whose contents are a function of the query.

That is a different accounting problem, and this collection has met it once before in a different field. A structure’s size at rest and its working memory during a query are separate bills, and a structure whose size is o(n) and whose query holds d bits has a term nothing in its own accounting mentions. The listing bitmap is d bits, which on two thousand documents is small; the phrase index’s visited set is proportional to the answer, which is not.

What the apparatus costs now

On sixty-four documents of two hundred and fifty-six characters, the published apparatus — D, C and a segment tree — is 838,797 bits beyond the suffix array and the text.

Removing C and making the range minimum succinct brings it to 285,460, a factor of 2.9.

Putting D in a wavelet tree, which is where the rest of this strand goes, brings it to 119,972 — 14.3% of where the strand started, and 42.0% of where the previous removal left it.

That last step does two things at once and they are worth separating. It makes D smaller, by the amount its symbol distribution allows. And it makes the range minimum unnecessary, because the distinct symbols of an interval are what a wavelet tree enumerates. The tree answers the question is that argument; this essay is about what is left to answer it about.

What “beyond the suffix array” is hiding

Every share quoted above is a share of an apparatus, and the apparatus sits on a base that is much larger than any of it. That base deserves a paragraph because it decides whether any of this matters.

On sixty-four documents of two hundred and fifty-six characters the suffix array is n⌈log₂ n⌉ bits and the text is n⌈log₂ σ⌉, and together they are 328,000 bits. The published apparatus was 838,797 — two and a half times the thing it sits on. That is the number the apparatus that is smaller than its index was named after, and it is why the strand kept going: an apparatus larger than its index is not an optimisation, it is a second index.

At 119,972 it is 36.5% of the base, which is a share a reader can consider paying. The whole arc of the strand is a structure going from two and a half times its base to a third of it, and none of the steps changed what the structure computes.

The share also explains why the removals came in the order they did. The chain was the single largest part — n⌈log₂ n⌉ bits, the same width as the suffix array — so removing it was worth more than everything else together. The range minimum was next. D was last because it was smallest, and it is only now the largest part because everything above it has gone.

The shape of the sequence

D is not an arbitrary sequence and its structure is the whole of what the rest of the strand is about.

Each document appears in D exactly as many times as it contributed characters. So D’s symbol distribution is the collection’s length distribution, exactly — not approximately, not statistically, but by construction. A collection of equal-length documents gives D a uniform distribution and an entropy of exactly log₂ d. A collection where one document holds four fifths of the text gives D an entropy near the binary entropy of four fifths.

That is a strong statement and it makes the compression question answerable in advance. The array is the length distribution is the measurement, and it comes out with a null result on the collection everybody pictures.

What the document array can be compressed to is the length distributionThe zeroth-order entropy of the document array, for four collections of 32 documents and 8,192 characters. The array holds each document once per character it contributed, so its symbol distribution is the length distribution and nothing else. On equal-length documents H0 is 5.000 against a ceiling of log2 32 = 5.000 — the two are the same number, and a wavelet tree over that array is 100.0% of the plain n⌈log₂ d⌉ array, which is to say it saves nothing. On a collection where one document holds four fifths of the text H0 falls to 1.688 and the array to 39.5%. The dashed line is the ceiling every row is measured against.equal lengths5.00 bits100.0%lengths as one over rank4.15 bits83.4%a few long, many short4.31 bits86.7%one document holding most of the text1.69 bits39.5%log₂ 3232 documents · 8,192 characters100.0% to 39.5%
Fig. 3 The entropy of the document array on four collections of the same size and the same document count, differing only in how the characters are apportioned.

The distribution is also the reason a compression result here needs its collection stated, and it is worth being blunt about how strong the effect is. On equal-length documents the entropy of D is log₂ d to three decimal places — the ceiling — so no coding of any kind saves anything. That is not a weak result about a particular coder; it is a statement that the sequence has no redundancy of the zeroth-order kind at all.

Real collections are not equal-length, and the departure is where the whole saving lives. A collection whose lengths fall as one over rank — which is what a real archive of documents tends to look like — gives an entropy of 4.15 against a ceiling of 5.00, and its array compresses to 83% of the plain one. A collection where one document dominates gives 1.69 and compresses to 40%.

Between the null case and the extreme is a factor of two and a half in what the last array costs, decided entirely by a property of the collection that has nothing to do with its text.

Why D is the last one

There is a reason to expect this strand to stop here, and it is not that nobody has thought of anything.

The three arrays formed a chain of derivations: the range minimum is about C, C is about D, D is about the collection. Removing a derived structure is always available when the thing it derives can be recomputed cheaply enough at query time, and each removal in this strand found exactly that. There is nothing below D to derive it from, because the collection’s document boundaries are the input.

What is available is representation. D can be stored more cheaply — that is the entropy question — and it can be stored in a form that answers more questions, which is the wavelet tree. Neither of those removes it.

The one thing that would remove D is a different formulation of the problem. If the query were which documents, answered by a structure over documents rather than over rows — an inverted index — then D would not exist, and neither would the suffix array. That is a different design with different costs, and the comparison between the two is not made here.

What each method's work is proportional toThree methods against the number of documents in the answer, on a collection where the range grows from 17 rows to 324. Reading every occurrence follows the range: 17 to 324, a factor of 19x. The range-minimum walk follows the answer with a logarithmic factor on top. The descent follows the answer with the least of the three: 36 operations for 7 documents and 62 for 32, which is 5.14 per document down to 1.94 — the work per document FALLS as the answer grows, because the ancestors of many leaves overlap near the root.1010010³documents in the answeroperations inside the structureevery occurrencerange minimumone descent17 to 324 rows5.14 to 1.94 per document
Fig. 4 What each method’s work is proportional to, against the number of documents in the answer, on a collection where the range grows by a factor of twenty-five.

There is a fourth thing that could go, and it is worth naming even though this strand does not take it: the suffix array itself. Every method here reads the rows of a pattern out of a suffix array and the suffix array is n⌈log₂ n⌉ bits, which is larger than the apparatus above it by a wide margin. An index larger than what it indexes is where that was first measured, and the index that is smaller than the text is where it was replaced with a self-index.

Replacing it changes the accounting above completely: the base against which the apparatus is a share falls by a factor of several, so an apparatus at 36% of a suffix array plus a text is a much larger share of a self-index. That comparison is not drawn in this strand, and it is the honest limitation of every share quoted here — the denominator is the uncompressed base.

What the document array can be compressed to is the length distributionThe zeroth-order entropy of the document array, for four collections of 64 documents and 16,384 characters. The array holds each document once per character it contributed, so its symbol distribution is the length distribution and nothing else. On equal-length documents H0 is 6.000 against a ceiling of log2 64 = 6.000 — the two are the same number, and a wavelet tree over that array is 100.0% of the plain n⌈log₂ d⌉ array, which is to say it saves nothing. On a collection where one document holds four fifths of the text H0 falls to 1.888 and the array to 36.3%. The dashed line is the ceiling every row is measured against.equal lengths6.00 bits100.0%lengths as one over rank4.86 bits81.6%a few long, many short5.31 bits88.9%one document holding most of the text1.89 bits36.3%log₂ 6464 documents · 16,384 characters100.0% to 36.3%
Fig. 5 The same four collections at twice the size and twice the document count. The ceiling rises by one bit and the null case stays exactly on it.

Doubling the document count raises the ceiling by a bit and moves the equal-length case exactly with it, which is what “by construction” means and is worth seeing twice. The three uneven collections move differently: the one-over-rank shape gains almost the whole bit, because doubling the documents doubles the number of small ones and the distribution’s shape is preserved; the dominated collection gains almost none, because its entropy is decided by the one large document and adding thirty-two more tiny ones changes very little.

So the saving — the gap between the entropy and the ceiling — grows with the document count on a skewed collection and stays at zero on an even one. That is the direction a reader would want, since the array is also getting wider, and it means the compression is worth more on exactly the collections where the array is largest.

The pattern across three removals

Three arrays, three removals, and the three had different reasons — which is worth noticing because it means there is no single trick being applied.

The chain went because a traversal order made its test answerable. The range minimum shrank because a representation held the same information in fewer bits. And the range minimum goes entirely, in the next essay, because a different structure over D answers the same question as a side effect of holding D.

Those are three separate techniques and only the first is specific to this problem. The second and third are general moves — represent it better, and check whether something already present answers it — and both were available from the first day.

That is the uncomfortable part of the summary. An apparatus that shrank by a factor of seven over three passes did not require any new idea about document listing. It required noticing three times that something already present was doing a job an extra array had been built for.

The smaller tree hands its answer back in the wrong orderThe same document array in two tree shapes, on a collection of 32 documents whose lengths fall as one over rank. The balanced tree costs 49,593 bits and returns the documents sorted, because its leaves are in the alphabet's order. The Huffman tree costs 41,589 — 83.9% of it — and returns them in frequency order. Both report the same 4 documents for the same query at almost the same operation count. So the trade is 8,004 bits against sorting an answer of 4, which is d log d comparisons on a walk that already cost more than that — and it is stated rather than hidden, because a plate showing only the Huffman column is showing a saving and not a choice.bits heldbalanced49,593huffman41,589the answer comes backbalanced: sorted · 26 operationshuffman: sorted · 24 operations32 documents · zipf lengths83.9% of the ordered tree
Fig. 6 The one place a choice remains: two representations of the same array, one smaller and one handing its answer back in the right order.

The habit that produced all three is worth stating, because it is the actionable part. When a structure holds an array whose entries are derived from another array it holds, ask what question the derived array answers and whether the traversal, the representation or a neighbouring structure already answers it. Three times out of three in this apparatus the answer was yes.

The corresponding warning is that each removal made the query do more. The chain’s removal added a bitmap and an order constraint; the succinct range minimum’s operations cost more than a segment tree’s per query; the wavelet tree’s descent does more work per document than a range-minimum step. The apparatus is seven times smaller and its query is not seven times cheaper — what the chain cost measured that trade for the first removal and found the work identical, which was a better outcome than expected and is not the general case.

Reading the four methods against each other

The four rows of the method plate are worth walking once, because they are the strand’s whole history in one picture and the ordering is not what a reader would guess.

Reading every occurrence holds only D. It costs the occurrences — fifty-four operations on the query drawn — and it is proportional to the range rather than to the answer, which is the defect the whole apparatus exists to fix.

The published range-minimum method holds D, C and the tree, and costs two hundred and sixty-seven operations inside the structure for the same eight documents. That is five times what reading every occurrence cost. The asymptotics are right and the constant is not: a range-minimum query on a segment tree visits a logarithmic number of nodes and the walk performs one per reported document plus one per pruned subrange, so the constant multiplying the answer is large.

Removing the chain brings it to eighty-three, which is still above the fifty-four that reading every row cost.

The descent over a wavelet tree brings it to thirty-two, which is finally below. That is the first method in the strand’s history that is both proportional to the answer and cheaper than the naive alternative on the collection it was measured on.

The shape reaches the entropy down to a bit a symbol, and the vector below itThree quantities per collection: the entropy of the document array, what a Huffman-shaped wavelet tree of plain bit vectors costs, and what the same tree with compressed blocks costs. On the first three collections the plain tree is within 0.6% of the entropy, which is Huffman's redundancy and is all it should be. On the skewed collection it is 1.98 bits against an entropy of 1.69 — because a Huffman code word is at least one bit, so a plain-vector tree cannot cost less than n bits however low the entropy goes. Compressed blocks reach 1.57, because a level that is nearly all zeros is a run and a run is where a block class costs nothing.024bits a symbolequalzipftwoSizesoneLargeone bit a symbolentropyplain vectorscompressed blocks32 documents1.26x on the skewed collection
Fig. 7 The floor under the last array’s compression: a Huffman code word is at least one bit, so a plain-vector tree cannot cost less than n bits however low the entropy goes.

That ordering says something about how the earlier essays should be read. The cost that is the size of the answer established the proportionality and drew the crossing where the constants put it. The apparatus has been shrinking ever since, and the crossing has been moving with it, and nothing in the strand had put the constant below the naive method’s until the last array became the whole apparatus.

What is measured from here

The rest of this strand is about the last array, and it has four questions in it.

How far D compresses, which is the length distribution and has a null case. What the floor under that compression is, which is a code word being at least one bit. Whether the tree that compresses it can also answer the query, which removes the range minimum. And what the answer’s order costs, which is the one remaining trade.

Four ways to divide the same text into the same number of documentsThe 32 document lengths of each collection, drawn as a stacked strip of 8,192 characters, with the entropy of the resulting document array beside it. The four hold the same text and the same document count; what differs is how the characters are apportioned, and that alone decides how far the array compresses. Equal lengths give a uniform distribution and 5.00 bits a symbol, which is the ceiling. Lengths falling as one over rank — the shape a real collection of documents tends to have — give 4.15. One document holding most of the text gives 1.69, and it is the only one of the four where the saving is worth the machinery.equal lengthsH0 = 5.00 bitslengths as one over rankH0 = 4.15 bitsa few long, many shortH0 = 4.31 bitsone document holding most of the textH0 = 1.69 bits32 documents, 8,192 characters, four apportionmentslongest document 6,595 characters5.00 to 1.69 bits
Fig. 8 The property that decides all four: how a collection apportions its characters between documents, which is what the last array is a record of.

None of those is about the chain, the range minimum, or the walk. The strand has moved from the machinery to the thing the machinery was reading, and that is where an apparatus ends up when its derived parts have been removed one at a time.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Document arrayDocument collectionDocument listingIndex sizeOutput-sensitivePrevious occurrence chainRange minimumSuffix arrayWavelet tree