The index that replaces the text

Two thousand documents of two hundred characters

Every collection this field has measured has been a dozen documents of ten thousand characters. A real collection is usually the other shape, and the other shape moves every term in a document index — one of them by a factor of seventy.

Every collection this field has measured here has had the same shape: eight or twelve documents, ten or twelve thousand characters each, joined into one string.

That is what a version history of one file looks like, and it is the case a compressed index is built for. It is not what a document collection usually is.

Twelve long documents, and two thousand short onesEach collection's documents drawn as a range: the shortest, the median and the longest. Every collection in this field until now has been the top three rows — a dozen documents of about twelve thousand characters each, which is what a version history of one file looks like. A real collection of 2,214 figure captions has a median of 230 characters and a spread of 7.4 between its ends. Everything a listing structure costs depends on which of these two shapes it is built over, and nothing in this collection had ever been measured on the second.① prose12,33512① code13,1508① revisions7,77410② essays12,33582② captions2302,214② history12,96314documentscharacters in a document① the first freeze · ② the secondmedian marked
Fig. 1 Six real collections drawn as a range — shortest, median, longest. The top rows are the shape this field has always measured; the fifth is the shape a collection of many documents actually has.

Here is the other shape, measured: 2,214 documents with a median of 230 characters, the shortest 80 and the longest 594. It is every figure caption in this collection, one document each, and it is real prose written to one form by one hand — which makes it the kind of collection a search box actually sits in front of.

Nothing about it is exotic. Every term in a document index changes anyway, and one of them changes by a factor of seventy.

The term that knows how many documents there are

A document index is four things beyond the text: a suffix array, an array saying which document each row belongs to, a chain of previous occurrences, and a range structure over the chain. A list of documents is not a list of occurrences is what they are all for.

Only one of them knows the document count. The document array is n⌈log₂ d⌉ bits — one document number per row of the index — and d is inside the logarithm.

What the listing apparatus costs, on both collection shapesThe document-listing structures as a multiple of the index they sit on — a suffix array and the text — on 131,072 characters cut two ways. The document array is the term that moves with the shape: 7 bits a row on 82 documents and 12 on 2214, which is most of a suffix array by itself. So the same three structures cost 2.44x the index on long documents and 2.64x on short ones, and the whole of the difference is a per-row number nobody quotes.2214 docs · segment tree2.64x132,166 chars2214 docs · succinct1.65x132,166 chars2214 docs · no chain0.93x132,166 chars82 docs · segment tree2.44x131,113 chars82 docs · succinct1.45x131,113 chars82 docs · no chain0.73x131,113 chars131,072 characters · overhead ÷ index1.00 is the index itself
Fig. 2 The listing apparatus as a multiple of the index it sits on, for the same 131,072 characters cut into 82 documents and into 2,214.

On 82 documents that is 7 bits a row. On 2,214 it is 12. The same collection, the same characters, the same suffix array — and the document array goes from 917,791 bits to 1,585,992, which is 67% of the suffix array beside it rather than 39%.

That is the whole of the difference between the two rows on the plate, and it is a number that appears in no size formula quoted in this field, because the formula is usually written O(n log d) and read as though d were small.

What the separators cost when there are two thousand of them

The second term is the one one separator or one for each priced, and its arithmetic was exactly right and its example was too small to show it.

A construction that needs every suffix comparable — a suffix array over a collection, a wavelet tree over its symbols — needs the documents separated by characters that occur nowhere else. One shared separator costs one symbol. A separator per document costs d symbols, and the alphabet is inside a logarithm in every size here.

A separator per document, priced at three document countsThe packed size of each collection — n⌈log₂ σ⌉ bits — with one separator between all the documents and with one separator per document. The second is what a construction needing every suffix to be comparable requires, and the document strand priced it as "nothing at all until σ + d crosses a power of two, and then a whole bit per character". On twelve documents it crosses nothing and costs exactly zero. On 2,214 it crosses twice: 7 bits a character becomes 12, which is 71.4% added to every character of every document for a punctuation mark nobody reads.12 docs · one separator1,012,319 bitsσ 9612 docs · one each1,012,319 bitsσ 97, 1.00x82 docs · one separator7,033,754 bitsσ 11582 docs · one each8,038,576 bitsσ 183, 1.14x2214 docs · one separator3,844,470 bitsσ 1152214 docs · one each6,590,520 bitsσ 2321, 1.71xpacked at ⌈log₂ σ⌉ bits a characterworst 1.71x
Fig. 3 Packed size with one separator and with one per document, at three document counts. The bars are n⌈log₂ σ⌉ bits.

On twelve documents the alphabet goes from 96 symbols to 97, ⌈log₂ σ⌉ stays at 7, and distinct separators cost nothing at all. That is the measurement this collection published, and it is exact.

On 82 documents the alphabet goes from 115 to 183, crosses 128, and the packed collection gets 14.3% larger.

On 2,214 documents the alphabet goes from 115 to 2,321, crosses 128 and 256 and 512 and 1,024 and 2,048, and seven bits a character becomes twelve. The packed collection gets 71% larger, for punctuation nobody reads.

The prediction was nothing at all until σ + d crosses a power of two, and then a whole bit per character. It crosses five times here. A claim that was true and looked like a rounding note turns into the largest single line in the size table, and the only thing that changed is the shape of the collection.

The artefacts, per character rather than in total

The third term is the strings a concatenation invents. The occurrences a join invents counted them: exactly (m − 1)(d − 1) windows span a join, and each of them is a string the index will report and no document holds.

That count follows the document count and not the size of the collection, which is a sentence that means very little at twelve documents and a great deal at two thousand.

The artefacts a join invents, per character of the collectionWindows spanning a document boundary, counted per thousand characters, on the second freeze's three parts at 131,072 characters each. The count is exactly (m − 1)(d − 1) on every row — it follows the number of documents and not the size of the collection — so cutting the same half-million characters into 2,214 documents rather than eighty produces 187.3 of them per thousand characters against well under one. Every one is a string an index over the concatenation reports and no document holds: on real text 99.8% of them occur nowhere at all.0501001502004681012window lengthspanning windows per thousand charactersessays · 82 docscaptions · 2,214 docshistory · 14 docs131,072 characters of each part187.3 per thousand at m = 12
Fig. 4 Spanning windows per thousand characters, on the same amount of text cut three ways. The curves are (m − 1)(d − 1) divided by n, and the axis is what makes that a rate.

The same 131,072 characters produce 405 spanning windows at six characters when they are 82 documents and 11,065 when they are 2,214. Per thousand characters that is 3.09 against 85.15 — a factor of twenty-eight, from cutting the same text into more pieces.

And they are almost all real artefacts rather than accidental strings: 97.0% of them occur in no document at all on the essays, 96.5% on the captions, and 100% on a version history whose documents are near-copies. At twelve characters the share is 100% everywhere, because a twelve-character string that straddles a boundary is essentially never a string the collection contains for another reason.

So the fix — a separator — is not optional on this shape, and the fix is the thing the previous section priced at 71%. That is the trade in one sentence: on many short documents either the separators are paid for or the wrong query is answered eighty-five times per thousand characters.

Why nobody measured this shape

It is worth asking why every collection in this field arrived as a dozen long documents, because the answer is not carelessness and it decides how much of the earlier work travels.

The first reason is that the compressed-index literature is largely about repetitive collections, and the canonical repetitive collection is a version history: successive revisions of one thing, each nearly identical to the last. That is genuinely a dozen documents of ten thousand characters, and it is the case where a self-index is worth building, because a structure proportional to the number of phrases in a parse is small exactly when the documents are near-copies of each other.

The second reason is that a generated collection makes the wrong shape easy. The model here — a base text, k copies, a fraction of the characters replaced — produces documents of exactly one length by construction, and the parameter that would make them many and short is the same parameter that makes each of them too short to hold a planted pattern several times. The dial that has no setting is about the model failing to reproduce a real history’s repetition; this is the same model failing to reproduce a real collection’s shape, one axis further out.

The third reason is that the earlier numbers were not wrong. A document array at 4 bits a row, a free separator, half a spanning window per thousand characters — those are the correct numbers for a dozen documents, and there was nothing in any of them to suggest that one of the three would grow by a factor of seventy on a collection that is no larger.

What makes it findable at all is having a real collection of the other shape lying around. This one is 2,214 captions written over the life of an essay collection: nobody made it to be a corpus, which is exactly what makes it worth measuring.

Which structures notice and which do not

Worth being precise about, because “everything gets worse” is not a measurement.

The suffix array does not notice. It is n⌈log₂ n⌉ bits whatever the collection is cut into, and n barely moves — 2,213 separator characters on 546,997 is 0.4%.

The text does not notice, unless the separators are distinct, in which case it is the 71% above.

The chain of previous occurrences does not notice. It is n⌈log₂ n⌉ bits: one row number per row, independent of how many documents those rows belong to.

The range structure does not notice, for the same reason — it is a structure over the chain’s shape.

The document array notices, at ⌈log₂ d⌉ bits a row.

And a reported bitmap, if a listing uses one, notices least of all: d bits in total, which is 2,214 bits against the 1.5 million the range structure costs. The array the walk never reads is what makes that substitution available, and this is the shape on which it is worth the most.

The apparatus against the index, as the documents multiply131,072 characters cut into more and more documents, with the listing apparatus priced as a multiple of the index it sits on. Both lines rise, because the document array is n⌈log₂ d⌉ bits and is the only term here that knows how many documents there are. The upper line is the published structure and never falls below 2.43x; the lower one has no chain, and stays under the index itself until about 2,214 documents. The saving is 76.3% at 4 documents and 64.7% at 2,214 — it shrinks, because what is left is the term that grows.1010010001documents in the collectionapparatus ÷ indexwith the chainwithout itthe index itself131,072 characters64.7% saved at 2,214 documents
Fig. 5 The apparatus against the index as the document count grows, with the chain and without it. Both lines rise, because the document array is the term that follows d.

What a real short document is like

The other half of “many short documents” is that they are short and uneven, and the unevenness is not decoration.

The lengths here run from 80 characters to 594, a spread of 7.4 — and documents that are not the same length is the essay about what that does to a listing’s cost model, which assumes nothing about lengths and is quietly measured on collections that have only one.

A collection trimmed to a common length is a different object. Cutting all 2,214 documents to the mean of 59 characters at the size these plates are drawn at would drop 1,226 of them below their own length and throw away 15.6% of the collection — and it would make every plate on this page look tidier and mean less.

That is the same discipline as keeping the alphabet real. A model that hands back documents of exactly 256 characters is answering a question about a collection that does not exist, and the answer will be right — it is the same arithmetic — and its constants will be somebody’s choice.

The one thing that gets easier

Not everything about many short documents is worse, and the exception is instructive.

A pattern occurring in a short document occurs in it once. On this collection the most frequent six-character string occurs 543 times across 522 documents, which is 1.04 occurrences a document — so the answer to “which documents” is nearly the same size as the list of occurrences, and every structure that exists to make the answer cheaper than the occurrences has nothing to do.

On many short documents there is nothing to be output-sensitive aboutThe collection's own most frequent 6-character strings, in 2,214 real documents of 132,166 characters. The upper line is what a scan over the occurrences reads and the lower is the size of the answer — and on this collection they are the same line, because a pattern that occurs 543 times occurs in 522 different documents. Occurrences per document is 1.04. Output-sensitive document listing exists to win when a pattern occurs many times inside each of a few documents, which is what a version history looks like and is not what a collection of short documents looks like at all.0200400200400occurrences of the patternrows read, and documents in the answerrows readdocuments in the answertwo thousand short documents · 2,214 documents1.04 occurrences a document
Fig. 6 The occurrences and the answer, on the collection’s own most frequent patterns. The two lines are the same line, which is the whole story of output-sensitive listing on this shape.

The crossing that never arrives is that result taken seriously. It is a negative one, and it is the most useful thing on this page for anybody choosing a structure: the apparatus that makes document listing output-sensitive costs bits proportional to the index and buys nothing at all on a collection whose documents are short.

The shape parameter, as an axis

The tidy way to hold all of this is to stop treating the document count as a property of a collection and start treating it as an axis, exactly as the character count is.

Fix the characters and sweep the documents. Every term then sorts itself into one of three behaviours, and the sorting is mechanical rather than a matter of judgement: the terms that do not contain d at all — the suffix array, the chain, the range structure, the text under a shared separator — are flat lines. The term that contains ⌈log₂ d⌉ is a staircase, rising by one bit a row each time d crosses a power of two. The term that contains d itself is the bitmap, which is d bits in total and is invisible at every point on the axis.

Drawn that way the plate stops being a comparison of two collections and becomes a statement about a family, and it says something a comparison cannot: the crossing point where the apparatus stops being smaller than the index it sits on is at about two thousand documents for this amount of text, and it moves with n because the flat terms grow with n and the staircase does not.

That is why the size plate on this page sweeps rather than tabulates. Two rows would have shown that many short documents cost more; the sweep shows which term costs more, that it is a staircase rather than a slope, and where the crossing is — which is the number somebody choosing a structure actually needs.

The other axis, the one this page holds fixed, is how long the documents are relative to the patterns being searched. That one decides whether the listing apparatus is worth having at all, and it has a page of its own.

The crossing is a step, and it is at a document length

Treating the document count as an axis pays off further than the section above takes it, because once every term is sorted into flat, staircase or invisible, the crossing can be solved for rather than read off a plate.

Write the chainless apparatus as bits a row. It is log2d\lceil\log_2 d\rceil for the document array plus a constant for the range structure, and the index it is measured against is another constant — neither of which contains dd at all. Two points from the size sweep pin both: 0.58 at four documents and 1.01 at 2,214. Since log24=2\lceil\log_2 4\rceil = 2 and log22214=12\lceil\log_2 2214\rceil = 12, ten bits a row are worth 0.43 of the index, so the index is 23.3 bits a row and the range structure is 11.5.

Both fall out right. The index measured elsewhere is 23.0 bits a row and the succinct range minimum is 11.3, neither of which was used to fit anything — so a two-parameter model built from two points on one sweep reproduces two numbers measured on another.

Now solve. The apparatus equals the index when log2d=11.8\lceil\log_2 d\rceil = 11.8, and a ceiling cannot take that value. At log2d=11\lceil\log_2 d\rceil = 11 the ratio is 0.97 and at 12 it is 1.01, so the crossing is not near two thousand documents — it is a step, at exactly 2,049. One document either side of a power of two moves the whole apparatus across the index it sits on.

That is the staircase this page identifies, doing the one thing a staircase does that a slope cannot: putting the interesting boundary at a place nothing about the collection chose.

And the movement with nn is now readable too, in the opposite direction to the intuition. Both constants are logarithms — the index is log2n+log2σ\lceil\log_2 n\rceil + \lceil\log_2\sigma\rceil and the range structure is flat — so doubling nn adds one bit to the index and therefore one bit to the dd the crossing sits at, which doubles the crossing’s document count. The crossing is proportional to nn, which means it is not a document count at all.

It is a document length, and it is about 64 characters. At the 131,072-character size these plates are drawn at, 2,048 documents is 64 characters each, and every larger collection cut the same way crosses at the same length.

Which is the sharpest available statement of what this page is about, and it explains the number the page opens with. The captions at this plate size average 59 characters a document — just under the threshold — and the apparatus measures 1.01 times its index. The collection is not merely on the wrong side of a crossing; it is five characters over the line, and a corpus of slightly longer captions would sit under it.

So the recommendation has a form nobody would guess from the document count. Do not ask how many documents there are. Divide the collection’s characters by its documents, and compare against sixty-four. Above it the listing apparatus is smaller than the index it sits beside; below it, it is not — and the array the walk never reads’s substitution is what moves the line, since removing the log2d\lceil\log_2 d\rceil term removes the only place dd appears and the crossing goes away entirely.

What this changes about the earlier numbers

Nothing published here is withdrawn, and three sentences need a shape attached to them.

The listing apparatus costs about two and a half times the index it sits on — true at 2.44 on 82 documents and 2.64 on 2,214, so the shape moves it by less than a tenth. That one travels.

A separator per document is free — true on twelve documents and on no collection larger than about a hundred.

Spanning windows are a small correction — true per document and false per character, by a factor of twenty-eight between the two shapes on this page.

The packed collection under three joins, at 15 documents15 documents of 256 characters. Running them together gives an alphabet of 21; one shared separator gives 22; one separator each gives 35. The alphabet sits inside a logarithm, so for most of the range the three cost the same — and then the largest crosses a power of two and every character of every document costs a whole extra bit, 1.20x the packed size. The cost of naming the documents is charged to the text.run together19,200 bitsσ 21 · 5 bitsone separator19,270 bitsσ 22 · 5 bitsa separator each23,124 bitsσ 35 · 6 bits15 documents of 256 charactersthe separators are the only differenceEnglish-like · 15 documents1.20x for the distinct marks
Fig. 7 The original measurement of the join, on the collection sizes the model could produce. Every number on it is right, and the axis stops one order of magnitude before the interesting part.

A last note on why this collection and not another. What the generated collection was right about drew the line between quantities a model gets right by arithmetic and quantities it gets right by a parameter, and the shape of a collection is squarely the second: documents per collection and characters per document are two numbers somebody types. Measuring them on something nobody built as a corpus is the only way to find out what they usually are.

The general lesson is the one this strand keeps producing. A collection has two shape parameters — how much text, and how it is cut — and this field has swept the first for years and held the second at a dozen.

What is still not measured

Two things, and the first is the one that would change a recommendation.

A repetitive collection of many short documents. Everything on this page is either prose or a version history, and the two shapes are confounded with the two kinds of repetition: the long-document collections here are the repetitive ones and the short-document collection is not. A real collection of thousands of near-identical short documents exists in quantity — log lines, product records, the same form filled in by different people — and it is the case where a compressed index and many documents meet. Nothing here says what the document array costs relative to a structure whose size follows the phrase count rather than the character count, and the answer is not obvious: one term follows n and the other follows z, and on a repetitive collection those differ by an order of magnitude.

And the query distribution. Every plate here measures the collection’s own most frequent strings, because a real corpus supplies its own patterns and a planted one does not. That is the right axis for a question about the structures, and it is not what a query log looks like: real queries are words rather than six-character windows, they are wildly unequal in frequency, and the ones people repeat are not the ones the text repeats. The listing apparatus is priced here against the text’s distribution, and pricing it against a query distribution would be a different measurement with the same structures in it.

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.

AlphabetArtefactCorpusDocument arrayDocument collectionDocument listingIndex sizeRange minimumScaleSeparatorSuffix array