What the libraries do

The crossing that never arrives

Output-sensitive document listing exists because a pattern can occur four thousand times in eight documents. On a real collection of two thousand short documents it occurs 1.04 times per document, and the whole apparatus buys nothing at all.

The argument for output-sensitive document listing is one sentence, and it is a good one.

A pattern occurring four thousand times in eight documents has an answer of size eight, so an algorithm proportional to the occurrences is doing five hundred times the work an algorithm proportional to the answer would.

Everything follows from it: the document array, the chain of previous occurrences, the range minimum over the chain, and the walk that visits the answer instead of the rows. The cost that is the size of the answer is the whole construction, and it works exactly as advertised.

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. 1 The collection’s own most frequent six-character strings, in 2,214 real documents. The upper line is rows read and the lower is the size of the answer — and they are the same line.

On a real collection of two thousand short documents, the sentence is false at every point that can be measured on it. The most frequent six-character string occurs 543 times in 522 documents. The next occurs 529 times in 510. Occurrences per document is 1.04, and everything below it is 1.00.

What the ratio has to be

The crossing is easy to state once the two costs are written down.

A scan reads every row of the pattern’s interval and collects the documents: cost is the occurrence count, and each unit is one array read.

The walk asks a range structure for the position of the smallest chain entry, reports a document, and recurses on both sides: cost is one query per document in the answer, plus one per pruned branch, and each query is not one read. On a segment tree it is a descent of ⌈log₂ n⌉ visits; on a succinct structure it is nine or ten memory accesses. The shape a range question is about is where that constant was priced.

So the walk wins when

occurrences × 1 > documents in the answer × cost of a query

which is to say when occurrences per document exceed the cost of one query, measured in array reads. That number was 32.2 when the range structure was a segment tree, and 10.8 once it was succinct — the crossing moved by a factor of three because a query got three times cheaper, which is the cleanest cross-strand result this collection has produced.

On the short-document collection the ratio is 1.04. It is not near either crossing; it is an order of magnitude below the friendlier of the two.

The same prose, cut the other way

The measurement is worth trusting because the control is available: the same essays, in the same collection, cut into 82 documents instead of 2,214.

On many short documents there is nothing to be output-sensitive aboutThe collection's own most frequent 6-character strings, in 82 real documents of 131,113 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 359 times occurs in 82 different documents. Occurrences per document is 4.38. 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.0100200300100200300occurrences of the patternrows read, and documents in the answerrows readdocuments in the answera million characters of prose · 82 documents4.38 occurrences a document
Fig. 2 The same query on the same prose, in 82 documents. The most frequent six-character string occurs 359 times in 82 documents — 4.38 a document — and the two lines separate.

Here the ratio is 4.38 for the most frequent pattern and between 1.65 and 3.85 for the rest. The walk reads 123 queries against 359 rows for the top pattern, so it does about a third of the work — and one third is not a win, because a query costs nine or ten reads. At 4.38 occurrences a document the scan is still ahead.

Two shapes of the same prose, then, and neither of them reaches the crossing. One of them is not close.

Why a short document cannot get there

The reason is arithmetic rather than a property of this corpus, which is what makes it worth a page.

A document of 230 characters holds 225 distinct six-character windows. A six-character string that occurs in it twice would have to be one of a small number of repeated windows in a passage of two sentences — and prose does not repeat itself at that scale. It repeats words, and a six-character window that captures a common word occurs once per use of that word, which in two sentences is once.

So occurrences per document is bounded above by roughly the document length divided by the length of the repeating unit, and for short documents that bound is close to one whatever the text is. The only way to get a large ratio is a document long enough to say the same thing many times.

That plate is honest and its axis is a planted parameter. The pattern is inserted into each holding document as many times as the sweep asks for, because a pattern drawn from a real collection of near-copies occurs once per document and there is nothing to measure. The planting is the right experimental design for the question does the method behave as claimed — and it means the axis is not a distribution any collection supplies.

What a query costs, and why it is not one

The crossing is a comparison between two units, and the whole difficulty is that one of them is a read and the other is not.

A scan’s unit is an array access: read the document number at row i, put it in a set. On a machine that is one cache line most of the time, because the rows of an interval are contiguous and the document array is read in order. It is about as cheap as a unit of work gets.

A walk’s unit is a range-minimum query, and what that costs depends entirely on which structure answers it. A segment tree over n values answers in ⌈log₂ n⌉ node visits, each a read of a node that is nowhere near the last one — 17 scattered reads at 131,072 values. The shape a range question is about replaced that with a structure holding the shape of the array and not its numbers, which answers in a constant number of steps: two selects, a block lookup and a rank, nine or ten accesses in all.

That is the difference between a crossing at 32 occurrences a document and one at 10.8, and it is worth stating as a general point about output-sensitive methods. The claim “proportional to the answer” is a claim about growth, and at the sizes anybody actually runs it is the constant that decides. A method whose cost is 10 × |answer| beats one whose cost is 1 × |occurrences| exactly when the occurrences are ten times the answer — which is a statement about the data, not about either algorithm.

The plate below is the same comparison drawn as work rather than as a threshold, and the flat line is the one the argument is about.

Where the crossing does live

The shape where occurrences per document is large is not exotic; it is just not this one.

A version history. Ten revisions of a file, each nearly identical: a string in the original occurs once per revision, so a phrase common in the file occurs many times in each document as well. This is the case the whole compressed-index family is built for, and it is the one a corpus that was not generated froze so that the numbers about it would be about a fixed object.

A collection of long documents. A book is half a million characters; a common word occurs thousands of times in it. The ratio is enormous, the answer is one document, and output-sensitive listing is worth every bit it costs.

Logs, records, and anything machine-generated. A boilerplate line occurs in every record of a large file, and the file is one document.

A collection whose documents overlap. Where the same passage appears in several documents — quoted, forwarded, copied — the occurrence count rises without the answer rising, which is the ratio the method wants. That is a property of a corpus’s provenance rather than of its subject, and documents that are not the same length is the nearest this collection comes to measuring it.

And a query that is a word rather than a window. Every measurement here is over six-character windows because that is what a text supplies. A real query is a word, and a word that occurs in a document usually occurs in it more than once — though on a two-sentence document, not much more.

The listing, swept over a corpus's own vocabulary12 essays, 144,617 characters, and the patterns are the corpus's own 6-character strings taken at falling frequency rather than planted at rising density. The most frequent occurs 388 times across 12 documents, which is 32.3 per document — the document strand's crossing was thirty-one, and its remark that "a phrase occurring thirty times in a document is not unusual" turns out to be right at the very top of the distribution and wrong below it. With a segment tree the chain beats the scan on 0 of the 6 patterns drawn. Both axes are logarithmic.100100occurrences of the patternreadsmost frequent ", and " · 388 occurrencesthe scanthe chain12 documents · 144,617 characters0 of 6 won
Fig. 3 The same crossing on the first frozen corpus’s twelve long documents, where the most frequent six-character string reaches 32.3 occurrences a document and the walk wins seven of twelve queries.

That plate is the one this page is arguing with, and the argument is not that it is wrong. On twelve long documents the ratio genuinely reaches 32; the walk genuinely wins; the structures are genuinely worth their bits. Both measurements are of real collections. They disagree because the collections are different shapes, and neither of them is the shape.

What it costs to be wrong about this

The apparatus is not free, and on the shape where it buys nothing it costs the most.

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. 4 The listing structures as a multiple of the index they sit on, for both shapes. The document array is 12 bits a row on the collection where the walk has nothing to win.

On 2,214 documents the apparatus is 2.64 times the index it sits beside — a document array at 12 bits a row, a chain as wide as the suffix array, and a range structure over the chain. That is the price of an ability the collection cannot exercise.

Removing the chain, which the array the walk never reads shows is never consulted, and which what the chain cost prices in full, takes it to 0.93. Removing the whole apparatus — keeping only the document array, and answering “which documents” by scanning the occurrences — takes it to 0.48, and costs nothing measurable on this collection because the scan was winning anyway.

So the recommendation for this shape is the blunt one: keep the document array, drop the listing structures, scan. It is smaller and it is faster, and the only thing it gives up is a guarantee about a regime the collection never enters.

The measurement this collection should have made first

There is a version of this page that could have been written before any of the structures were built, and it would have taken an hour.

Take the collection. Count, for each of its own frequent strings, how many occurrences and how many documents. That is a suffix array and a loop over an interval — no chain, no range structure, no listing algorithm at all. The answer is the ratio the whole apparatus’s usefulness depends on, and it is available before a single bit of the apparatus exists.

This collection has a name for that habit: compute the break-even first. It is the rule that came out of a slate where three of five strands would have been settled by a one-pass measurement — a mean run against a code’s crossing, a depth histogram against a cap sweep, a run-length comparison against a substitution model — and where the expensive route was taken in each case and produced something extra. Here the cheap route would not have produced something extra. It would have said, in one plate, that the collection the structures were about does not reach the regime they win in.

What makes that worth stating rather than regretting is that the structures were still worth building. A list of documents is not a list of occurrences is a real distinction and the walk is a real algorithm; the succinct range minimum built for it turned out to decide the crossing for the other shape of collection, where it moved the answer from two of twelve queries to seven of twelve. The measurement that should have come first would not have stopped any of that. It would have changed which sentence went at the top.

How to tell, in one measurement

The useful form of all this is a check somebody can run before building anything.

Take the collection. Take a hundred of its own most frequent strings at the length queries actually are. For each, count occurrences and count distinct documents. Take the ratio.

If the median ratio is above about ten, build the apparatus: the walk will win, and the succinct range structure makes the crossing low enough that it wins on most queries rather than the extreme ones. If it is between two and ten, the answer depends on the query distribution and on what a query costs on the machine in question, and the honest thing is to measure both methods rather than reason about them. If it is near one — and it is near one for every collection of short documents — the apparatus is bits spent on nothing.

Where the two methods meet, with each range structure12 documents of 2,048 characters, the pattern planted at rising density in 3 of them, so the answer stays at 12 documents and only the occurrence count moves. The scan costs one read per occurrence and rises along the diagonal. The chain costs 412 reads with a segment tree and 135 with the succinct structure, near enough flat in both cases — so changing the range structure moves the crossing from 34 occurrences per document to 10. The strand that named this structure predicted "a handful"; it is a factor of 3.41x, because a constant-time query is a dozen lookups rather than one.100100occurrences of the patternreadsthe scanchain, treechain, succinct12 documents · answer 12crossing 406 → 119
Fig. 5 Why the threshold is a number rather than a constant: the crossing depends on what one range query costs, and that moved by a factor of three when the range structure changed.

The measurement takes a suffix array and a loop. It is cheaper than the structures it decides about by three orders of magnitude, and nothing in the published account of the method suggests making it, because the published account is about the regime where the answer is already known.

The document count is not only what keeps the crossing out of reach. It is also what a join has to be protected against, and the same ratio drives both.

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. 6 Windows spanning a document boundary, per thousand characters of collection, on the second freeze’s three parts at 131,072 characters each. The count is exactly (m1)(d1)(m-1)(d-1) on every row — it follows the number of documents, not the size of the collection — so cutting the same text into 2,214 documents rather than eighty produces 187.3 of them per thousand characters against well under one, and 99.8% of them occur in no document at all.

So the shape that makes output-sensitive listing pointless is the shape that makes the join’s artefacts numerous. One collection parameter, two consequences, and neither of them visible on a collection built out of a dozen long documents.

What this does to the strand

Three sentences, and the middle one is the retraction.

The construction is correct and the sweep supporting it is correctly drawn. Output-sensitive listing does what it says on a collection where occurrences exceed documents, and this collection has measured that on real text.

The regime is narrower than the strand implied. Every plate here that showed the method winning was drawn on twelve long documents or on a planted sweep, and the shape a document collection usually has does not reach the crossing at any query the collection itself supplies. That was never checked because the collection to check it on did not exist.

And the general habit is the one this strand keeps arriving at: a method’s cost model is an input, not a property. The cost model is an input says it about the machine; this says it about the data. A structure whose advantage is a ratio is only an advantage on collections where the ratio is large, and the ratio is a property of a shape nobody was sweeping.

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. 7 And the collections, again, because every number on this page is a number about one of these and the disagreement between two of them is the whole result.

Length is not the variable, and three measurements say so

The explanation offered above is a length argument: a document of 230 characters cannot hold the same six-character string many times, so the ratio is near one whatever the prose is. That is true and it is not the whole mechanism, because this page already carries three measurements and they do not line up on length.

Put them in order. The captions are about 230 characters each and give 1.04. The essays are the same prose cut into 82 documents instead of 2,214, so each is about twenty-seven times longer — call it six thousand characters — and gives 4.38. The twelve long documents of the frozen corpus are 2,048 characters each and give 32.3.

The middle collection has documents three times longer than the last and a seventh of its ratio. Length cannot be the variable, or that row is impossible.

What separates them is what the documents are. The twelve-document corpus is a set of near-copies — the collection a corpus that was not generated froze precisely because it repeats — so a string appearing once in the source appears once in every revision, and a frequent string appears many times in each. The essays are eighty-two distinct pieces of prose, and prose does not say the same six characters over and over however long it runs.

So the ratio has two inputs and the page has been attributing it to one. Length sets a ceiling — 230 characters cannot hold eleven occurrences of anything, so the captions could not reach the crossing whatever they contained — and repetition decides where under the ceiling a collection actually sits. The essays have the headroom and do not use it.

That reorders the deferrals below. A sweep of the ratio against pattern length varies the axis with the least leverage on this page: between the two long-document collections, at a fixed pattern length of six, the ratio moves by a factor of 7.4 on nothing but provenance. A pattern-length axis would have to move it further than that to be the more informative experiment, and there is no reason to think it can — occurrences and documents both fall as the pattern lengthens, so the ratio is a quotient of two shrinking numbers rather than a trend with a direction anybody can predict.

The measurement worth making instead is the one this page keeps recommending and has now run three times by accident: take four or five collections of genuinely different provenance, hold the pattern length fixed, and read the spread. It is the same suffix array and the same loop, it costs the same hour, and the answer it gives is the one a reader deciding whether to build the apparatus actually needs — because provenance is the thing they know about their corpus before they index it.

It also sharpens the recommendation into something checkable without any measurement at all. A collection of short documents cannot reach the crossing, by the ceiling argument, and two thousand documents of two hundred characters is that case. A collection of long documents may or may not, and which one it is depends on whether the documents are versions of each other. Nothing about being long is sufficient, which is the half of the recommendation these three rows add.

What is deferred

A query log. Every ratio here is measured over the collection’s own most frequent windows, because that is what a text supplies and a planted pattern is a parameter. Real queries are words, they are unequally frequent in a way the text’s own windows are not, and the ratio under a real query distribution is a different number that nothing here estimates.

A repetitive collection of short documents, which is the one cell of the two-by-two this page does not fill: long and repetitive is the version history, long and diverse is the essays, short and diverse is the captions, and short and repetitive — logs, records, generated documents — is where a compressed index and a large document count meet.

And the ratio as a function of pattern length. Six characters is one point on an axis. A longer pattern occurs less often and in fewer documents, and both terms of the ratio fall; whether the ratio itself falls, rises or holds is a sweep of one parameter over a real collection, and it decides whether the recommendation here depends on what people search for.

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

The 8 essays that link to this one and share the most of its objects, of 10 that link here.

The objects this essay names

Each one links to every other essay that touches it.

CorpusCrossingDocument collectionDocument listingIndex sizeOccurrenceOutput-sensitivePracticeQueryRange minimumSuffix array