The data that is not a number

The array is the length distribution

The document array holds each document once per character it contributed, so its symbol distribution is the collection's length distribution exactly. On equal-length documents its entropy is log d and no coding saves anything.

The document array is one symbol per row of the suffix array, saying which document that row’s suffix belongs to. It is n⌈log₂ d⌉ bits stored plainly, and on a collection of any size that is a substantial array — the same width as the text over a σ-symbol alphabet whenever d and σ are comparable.

The question of how far it compresses has a complete answer, and the answer is not a measurement of the array. It is a measurement of the collection.

Each document appears in the array exactly as many times as it contributed characters to the collection. So the array’s symbol distribution is the collection’s length distribution — not approximately, not in expectation, but by construction and to the character. Whatever a zeroth-order coder can save on the array is whatever the length distribution’s entropy is below log₂ d, and that quantity is computable from a list of file sizes before anything is built.

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. 1 The entropy of the document array on four collections of thirty-two documents and eight thousand characters, differing only in how the characters are apportioned between them.

The null case, measured

On a collection of thirty-two equal-length documents the entropy of the document array is 5.000 bits a symbol. The ceiling, log₂ 32, is 5.000.

A Huffman-shaped wavelet tree over that array costs 40,960 bits. The plain array costs 40,960 bits. The compression is exactly nothing, to the bit, and there is no coder that would do better — the sequence has no zeroth-order redundancy of any kind.

That is worth measuring rather than mentioning because the equal-length collection is the one everybody pictures. A benchmark cut into fixed-size blocks, a set of records of one format, a corpus divided evenly for parallel processing: all of them have a uniform document array, and all of them are cases where an entire strand of compression buys zero.

It is also worth measuring because a check that only asked “is the wavelet tree smaller than n⌈log₂ d⌉” would pass whenever d is not a power of two, on the rounding alone. At d = 33 the plain array costs six bits a symbol and the entropy is 5.044, so a “saving” of 16% is reported for a collection that has no redundancy at all. A saving quoted without its collection is the rejection test that catches exactly that.

There is a further consequence of the null case that is easy to miss and decides whether the strand is worth anything at all. If the document array cannot be compressed on a uniform collection, then on a uniform collection the entire last step of this apparatus — putting D in a tree — is a cost rather than a saving, because the tree carries directories the plain array does not. The step is still worth taking, because it removes the range minimum, but the size argument for it is void and the query argument is the whole of it.

That is a different claim from the one this strand opened with, and it is the honest version. The last array in the apparatus reported the apparatus falling to 14.3% of the published one; on an equal-length collection the same comparison holds, and the fall is entirely the range minimum going rather than the array shrinking.

Where the saving actually is

Real collections are not equal-length, and the departure is the whole of what there is to win.

Lengths falling as one over rank — the shape a directory of real documents tends to have, with a few long ones and a tail of short ones — gives an entropy of 4.15 against a ceiling of 5.00. The array falls to 83% of plain.

A few long documents and many short ones, at an eight-to-one ratio, gives 4.31 and 87%.

One document holding four fifths of the collection gives 1.69 and 40%.

So the range across realistic shapes is a factor of two and a half, and the extreme case is where the array nearly vanishes. That is not a coincidence: a collection dominated by one document is a collection where most rows carry the same symbol, and a code that spends one bit on the common symbol and many on the rare ones is exactly what a frequency-shaped tree is.

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. 2 The four length distributions themselves, drawn as a strip of the same total characters, with the entropy each produces beside it.

It is worth writing the extreme out, because it shows where the bound goes. A collection of d documents where one holds a fraction p of the characters and the rest split the remainder evenly has an entropy of H(p) + (1 − p) log₂(d − 1), where H is the binary entropy. At p = 0.8 and d = 32 that is 0.72 + 0.2 × 4.95 = 1.71, and the measured figure is 1.69 — the small gap being that the collection’s small documents are not exactly equal.

That closed form says something useful about which direction to look. The second term dominates unless p is near one, so a collection with one large document and many others is not especially compressible; it is a collection where the large document’s share is genuinely most of the text that wins. At p = 0.5 with thirty-one others the entropy is 3.5, which is 70% of the ceiling — a modest saving for a very lopsided-sounding collection.

Why the shape is enough, without a compressed vector

The mechanism by which a wavelet tree reaches this bound is worth being explicit about, because it is often attributed to the wrong part of the structure.

A wavelet tree over a sequence, shaped by a prefix code, stores one bit per symbol per level of that symbol’s code. A symbol whose code is three bits long contributes three bits, once for each level its path passes through. Summed over the sequence, the tree’s total payload is Σ nᵈ · |code(d)| — the weighted path length of the code, which for a Huffman code is within a bit a symbol of the entropy.

That needs no compressed bit vector. Plain vectors, one bit per bit, and the right shape are the whole of it.

This is the opposite of the situation with the Burrows–Wheeler transform, where the shape buys nothing — the transform is a permutation, so its zeroth-order entropy is the text’s — and the compressed vector is what buys everything. Rank is the only thing it does is where that split was measured for the transform, and the document array is the mirror case: the shape does the work and the vector adds a little on top.

The reason for the difference is that the document array’s redundancy is zeroth-order. Its symbols are unevenly frequent and that is all. The transform’s redundancy is in its runs, which is a higher-order property that a frequency-shaped code cannot see.

What the tree costs beyond the entropy

Two terms sit on top of the payload and both are in the o(n) that “n·H₀ + o(n)” hides.

The rank directories are the superblock and block counters that make a rank on each level constant-time. On this collection they are about 20% of the payload, and the share is remarkably stable across the four length distributions — 19.5%, 20.7%, 20.9%, 23.3%. That stability is not surprising: the directory is a function of the vector’s length rather than of its contents, and the levels’ total length is the payload.

The code table is one code length and one symbol per document, so d(2⌈log₂ d⌉) bits — a hundred and sixty bits per document at d = 32, which is negligible until d gets into the thousands and then is not.

So the honest figure for what the array costs is the payload plus a fifth, and on the equal-length collection that means the wavelet tree is 20% larger than the plain array. The compression is negative in the null case once the machinery is counted, which is the second reason the null case has to be drawn.

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. 3 Three quantities per collection: the entropy, what a plain-vector tree costs, and what compressed blocks cost. The dashed line is one bit a symbol.

The distinction generalises past these two arrays, and it is a useful thing to carry when meeting a new sequence. Ask whether the redundancy being exploited is in the symbol frequencies or in their arrangement. Frequencies are what a code shape captures and what a plain-vector wavelet tree therefore captures for free. Arrangement is what a compressed vector, a run-length encoding or a copy-based parse captures, and no code shape reaches it.

The document array’s redundancy is frequency. The transform’s is arrangement. A collection’s own text has both. Three sequences in one structure, each wanting a different mechanism, which is why a single number for “how compressible is this index” has never been available here.

Two collections that look the same

The four distributions above are constructed, and it is fair to ask whether the spread between them is an artefact of construction. It is not, and the reason is that the construction is exactly what a collection builder does.

A collection of d documents totalling n characters can have any length distribution summing to n. Nothing about the text constrains it. So two collections with identical texts, identical alphabets, identical entropies and identical document counts can have document arrays differing by a factor of two and a half in size, and the only thing separating them is a list of d integers.

That is a stronger version of the point a collection is a construction makes about boundary placement. There the separating quantity was the characters immediately before d positions; here it is the d lengths themselves. Both are properties of the partition rather than of the bag of characters, and both decide a size that a corpus description does not mention.

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. 4 The one remaining choice about the representation, on the one-over-rank collection: two tree shapes, one smaller and one returning its answer in order.

That framing also settles a question about which tree shape to use, at least on the size axis. A balanced tree spends ⌈log₂ d⌉ bits on every symbol regardless of frequency, so it costs exactly what the plain array costs and the whole saving is forgone. A Huffman-shaped tree spends the code length. On the one-over-rank collection the two are 49,593 bits and 41,589 — a 16% difference, which is the entropy deficit reappearing as a size.

So on the size axis the frequency-shaped tree wins by construction and there is nothing to weigh. What makes it a choice at all is the order the answer comes back in, which is a separate axis entirely and is the smaller tree hands it back unsorted.

The two shapes a real collection has

It is worth being concrete about which of the four rows a reader is likely to be on, because the answer is usually the same one.

A collection of files in a directory — source code, documents, images’ metadata, log files — has lengths that are approximately log-normal, which for the purposes of an entropy is close to the one-over-rank row. Expect around 80–85% of plain.

A collection of records in a fixed format — rows of a table, fixed-length keys, protocol messages — is close to equal, and expect nothing.

A collection with one dominant member — a large document with a set of small annotations, an archive with one big file — is the fourth row, and the saving is large.

A collection cut into fixed-size blocks by an indexing system is exactly equal by construction, and the saving is exactly zero. That case is worth flagging because it is what happens to a collection on its way into an index, and a system that fragments its input into uniform blocks has destroyed the only redundancy the document array had.

What the document array can be compressed to is the length distributionThe zeroth-order entropy of the document array, for four collections of 128 documents and 32,768 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 7.000 against a ceiling of log2 128 = 7.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 2.101 and the array to 34.1%. The dashed line is the ceiling every row is measured against.equal lengths7.00 bits100.0%lengths as one over rank5.55 bits80.0%a few long, many short6.31 bits90.5%one document holding most of the text2.10 bits34.1%log₂ 128128 documents · 32,768 characters100.0% to 34.1%
Fig. 5 The same four collections at four times the document count. The ceiling rises by two bits and the equal-length case rises exactly with it.

One collection shape is missing from those four and is worth naming because it is common and behaves differently: a bimodal collection, where documents come in two sizes with nothing between. An archive of large data files and small manifests, a corpus of articles and their abstracts, a repository of source and tests. Its entropy is a mixture and its deficit depends on how the mass splits between the two modes rather than on either mode’s size.

The “a few long, many short” row above is a two-mode collection at an eight-to-one ratio, and its entropy of 4.31 against a ceiling of 5.00 is a 13% saving. Push the ratio and the saving grows; push the count ratio the other way and it shrinks. Neither the mean length nor the median predicts it, which is the reason a distribution rather than a summary is what this measurement needs.

What scaling the document count does

Raising d raises the ceiling by log₂ of the factor, and the four rows respond differently.

The equal-length row rises exactly with the ceiling, always and by construction. Its saving stays at zero at every d.

The one-over-rank row gains nearly the whole increase, because quadrupling the document count quadruples the number of small documents and the distribution’s shape is preserved — a one-over-rank distribution is scale-invariant in the relevant sense.

The dominated row gains almost nothing, because its entropy is decided by the one large document and adding more tiny ones changes very little.

So the absolute saving — the gap between entropy and ceiling, times n — grows with the document count on every collection that is not uniform. That is the direction a reader wants, because the plain array is also getting wider, and it means the compression is worth most on the collections where the array is largest.

What this settles and what it does not

It settles the size question completely: the document array’s compressibility is the collection’s length distribution’s entropy deficit, computable from a list of file sizes, with no measurement of the text required.

It does not settle the cost question, because a wavelet tree over the array is not merely a smaller array — it is a structure that answers different questions than an array does. The tree answers the question is where that becomes the point: the same tree that compresses D also enumerates the distinct symbols of an interval, which is the listing query, which removes the range minimum.

So the saving measured here is the smaller half of what the representation is worth. A collection getting 17% off its document array would probably not bother; a collection getting 17% off its document array and removing an entire range-minimum structure is a different proposition.

And it does not settle the floor, which is the subject of the next essay. 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 — and the dominated collection’s entropy of 1.69 is already close enough to that floor for the gap to be visible.

A collection that fragments its input into uniform blocks has also, incidentally, destroyed the property a collection is a construction found deciding the boundary cost: arbitrary block boundaries end at arbitrary characters, so the run count pays about a run per block on top of the array saving nothing. Two costs, from one decision, neither of them mentioned when the decision is made.

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. 6 Where the array sits in the apparatus it is now the whole of: three listing structures priced, with the compressed array as the last of them.

Where this sits against the earlier measurement

This collection has met the length distribution once before, in a different role, and the two results are worth putting side by side.

Documents that are not the same length asked what uneven lengths do to a listing — whether a collection with one dominant document changes the cost of finding which documents hold a pattern. The answer there was about the query’s work.

This asks what the same distribution does to a size, and the answer is exact rather than empirical: the array’s entropy is the distribution’s entropy, full stop. There is no modelling step, no fitting, and no measurement of the text.

That exactness is rare in this collection and it is worth saying why it happens. Most size questions here need a measurement because they depend on a property of the text that has no closed form — the run count, the phrase count, the copy depth. This one depends on a property of the partition, and a partition is a list of integers whose entropy is arithmetic.

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. 7 What the array is for, once it is compressed: four methods answering one query, three of which hold structures the fourth does not.

The general form is worth carrying because it says which measurements can be skipped. A quantity that depends only on the collection’s metadata — its file sizes, its document count, its alphabet — can be computed. A quantity that depends on the collection’s content has to be measured. Every size in the listing apparatus is now in the first category, and every size in the index below it is in the second.

The number to compute first

The practical form is one line and it needs no code.

Take the collection’s file sizes, normalise them to a distribution, compute −Σ p log₂ p, and compare against log₂ d. The difference, times n, is the number of bits a compressed document array will save, to within Huffman’s redundancy and the directories.

If that number is zero the collection is uniform and the representation buys only the query. If it is large the collection is dominated by a few documents and the representation buys both. Either way the answer is available before any index exists, and it is a property nobody has to measure the text to obtain.

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.

DistributionDocument arrayDocument collectionDocument listingEntropyHuffman codeIndex sizeWavelet tree