The floors

Work that falls as the answer grows

Output-sensitive usually means the cost rises with the answer instead of with the input. A descent over a document array costs five operations per document at an answer of seven and two at an answer of thirty-two, because the paths to many leaves share their tops.

An output-sensitive method is one whose cost follows the size of the answer rather than the size of the input. The phrase is usually taken to mean linear in the answer, and for the methods this apparatus started with that is right.

The descent over a compressed document array is not linear in the answer. It is sublinear, and the reason is a counting fact about trees that has a consequence for how the method should be reported.

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. 1 Three methods against the number of documents in the answer, on a collection where the row range grows from seventeen to three hundred and twenty-four.

The sweep

Thirty-two documents, a pattern planted in a controlled number of them, and the number of holding documents swept from one to thirty-two. The row range grows from seventeen to three hundred and twenty-four — a factor of nineteen — and the answer from seven documents to thirty-two.

Reading every occurrence goes from seventeen operations to three hundred and twenty-four. That is the range, exactly, because that is what it reads.

The range-minimum walk goes from a hundred and fifty-four to eight hundred and eighty-six.

The descent goes from thirty-six to sixty-two.

Per document: 5.14 operations at an answer of seven, and 1.94 at an answer of thirty-two. The work per document falls by a factor of 2.65 while the answer rises by a factor of 4.6.

Those three lines are three different functions and it is worth naming them before explaining any of them. The first is the range. The second is the answer times a logarithm times a constant of about twenty. The third is the answer times a logarithm that shrinks as the answer grows. Only the third has a shape that is not one of the two things a cost is usually proportional to.

Why it falls

A descent enters exactly the ancestors of the leaves it reports. The ancestors of one leaf are a root-to-leaf path, log₂ d nodes. The ancestors of two leaves are two paths that share their top, so fewer than 2 log₂ d. The ancestors of k leaves in a tree of d number at most k(1 + log₂(d/k)), which is a standard counting bound and is tight when the leaves are spread evenly.

At k = 1 that is 1 + log₂ d, or six on thirty-two documents. At k = d it is d, every leaf and its one distinct ancestor’s worth, which is 2d − 1 nodes for the whole tree.

So the per-leaf cost is (1 + log₂(d/k)), which falls as k rises and reaches one when k = d. Two ranks a node makes the operation count 2k(1 + log₂(d/k)), and dividing by k gives 2(1 + log₂(d/k)) operations a document.

Substituting: at k = 7 and d = 32 that is 2(1 + 2.19) = 6.4, and the measured figure is 5.14. At k = 32 it is 2, and the measured figure is 1.94. The bound is loose by about twenty per cent because the leaves are not evenly spread, and the shape is exactly right.

The same counting fact appears in two other places in this collection and it is the same fact each time. A trie over k keys of length m has fewer than km nodes because prefixes are shared; a merge of k sorted lists costs n log k rather than n log n because the tree is shallower; and here, k leaves have fewer than k log d ancestors. In every case the saving is that paths overlap near the root and the overlap is worth log(d/k) rather than log d.

What makes it visible here rather than absorbed into a constant is that k moves over the sweep. In a trie the key count is fixed by the input; here the answer’s size is the axis, so the discount is drawn rather than assumed.

What that does to the phrase

“Proportional to the answer” has a constant in it, and the constant here is not constant.

A method costing c·k operations for an answer of k has a single number c that can be quoted, compared and used to find a crossing. A method costing 2k(1 + log₂(d/k)) does not: its effective constant is 6.4 at one answer size and 2 at another, on the same collection.

That matters for the crossing. Reading every occurrence costs the range, which on this collection is roughly ten times the answer. So the descent beats it when 2(1 + log₂(d/k)) < 10 × (range/answer per document), and the left-hand side is moving.

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 One query in detail: twenty-eight occurrences in fifty-four rows, eight documents, and what each of the four methods spends to report them.

There is a second thing “proportional to the answer” hides, and it is the one that bit the earlier methods. Two methods can both be linear in the answer and differ by a factor of twenty in their constants, which is what the range-minimum walk and the descent do here — 886 operations against 62 for the same thirty-two documents. Asymptotically identical, and one of them loses to reading every row on every collection this field has and the other does not.

That is the general reason the cost that is the size of the answer drew a crossing rather than reporting a class. A class tells a reader which method wins eventually; a crossing tells them whether eventually has arrived.

The comparison that decides

At an answer of seven the descent costs thirty-six operations and reading every occurrence costs seventeen. The naive method wins.

At an answer of thirty-two the descent costs sixty-two and reading every occurrence costs three hundred and twenty-four. The descent wins by a factor of five.

Somewhere between an answer of seven and an answer of sixteen the two cross. On this sweep the crossing is at an answer of about ten documents out of thirty-two, which corresponds to a range of about eighty-six rows — that is, about nine occurrences a document.

That number should be read carefully because it is a crossing in the range, not in the density. The relevant question is whether the range is large relative to the answer, and on this planted collection the range grows faster than the answer because more holding documents means more occurrences per holding document too.

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 29 rows to 650. Reading every occurrence follows the range: 29 to 650, a factor of 22x. The range-minimum walk follows the answer with a logarithmic factor on top. The descent follows the answer with the least of the three: 84 operations for 17 documents and 126 for 64, which is 4.94 per document down to 1.97 — the work per document FALLS as the answer grows, because the ancestors of many leaves overlap near the root.10010³documents in the answeroperations inside the structureevery occurrencerange minimumone descent29 to 650 rows4.94 to 1.97 per document
Fig. 3 The same sweep at twice the document count, where the ancestor discount is larger because the tree is deeper and the answers are a smaller share of the leaves.

Doubling the document count deepens the tree by one level, so the per-document cost at a fixed answer size rises by two operations, and the discount at a full answer grows because log₂(d/k) has further to fall. The two effects push in opposite directions and the second wins at large answers: the descent’s advantage over the naive method is larger on a collection with more documents, which is the direction a reader would want and is not automatic.

Why the earlier crossing was so much worse

The crossing that never arrives measured the published apparatus and put its crossing at 10.8 occurrences a document. That structure held a chain, a segment tree and the document array, and its walk cost a range-minimum query per reported document plus one per pruned subrange.

The descent’s crossing on the same kind of collection is around three and a half, and the reason is entirely the constant. Both methods are proportional to the answer in the asymptotic sense; one of them pays a segment-tree traversal per document and the other pays two ranks per shared ancestor.

That is a large enough change to alter the conclusion of an earlier essay, and it does not alter it enough. One copy per document is one occurrence per document measures the density on every collection shape available here and finds 1.84 to 1.88 for a pattern a reader would ask for. A crossing at three and a half is still above that.

So the apparatus went from losing by a factor of two and a half to losing by a factor of about two, and it is still losing on the collections that exist.

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. 4 The size half of the same comparison: three apparatus priced, where the descent’s is the array itself.

There is a fair objection to that comparison, which is that the two methods are being run on different structures — the walk on a chain and a segment tree, the descent on a compressed array — so the operation counts are not commensurable. A segment-tree node visit and a bit-vector rank are not the same unit.

The answer is that both are counted the same way here: an operation is a read of a directory entry or a node, charged once. That is a coarse unit and it is the one this collection has used throughout, and its coarseness cuts against the descent rather than for it, because a bit-vector rank on a compressed vector is genuinely more expensive than a segment-tree comparison. So the factor of fourteen between 886 and 62 is a lower bound on the descent’s advantage in operations and an overstatement of it in time.

Quoting it as a factor of fourteen in time would be the error the unit of cost is not one exists to prevent, and it is not quoted that way here.

The floor the descent cannot get under

There is a floor and it is worth stating because it bounds what any further improvement can achieve.

Reporting k distinct documents requires distinguishing them, and distinguishing k things out of d requires at least k log₂(d/k) bits of information to be read — an information-theoretic bound rather than a property of any structure. The descent reads 2k(1 + log₂(d/k)) bit-vector ranks, and each rank returns a number rather than a bit — the distinction rank is the only thing it does is built on, so the comparison is not immediate; but the shape of the descent’s cost is the shape of the bound.

That is unusual in this collection. Most structures here have a cost whose shape is an artefact of the structure — a segment tree’s log is the tree’s depth, a chain’s cost is a comparison per row — and this one has a cost whose shape is the problem’s.

It means the descent is not merely better than the range-minimum walk; it is within a constant of the best possible shape for the reporting part of this problem. What it does not bound is the searching part, which is finding the row range in the first place, and that is the suffix array’s job and is unaffected.

The other direction: early stopping

The sublinearity has a practical consequence that nothing in this apparatus exploits and a real system would.

A search interface showing ten results does not want all four hundred documents holding a pattern. It wants ten. The descent visits leaves in a definite order — the tree’s leaf order — so it can stop after ten, at a cost of 2 × 10 × (1 + log₂(d/10)) operations rather than the full answer’s.

On a collection of ten thousand documents with an answer of four hundred, reporting ten costs about 200 operations against the full answer’s 3,900. That is a factor of twenty for a query nobody would notice was truncated.

The range-minimum walk supports the same thing and reading every occurrence does not, which is a genuine advantage of both output-sensitive methods that no measurement in this strand has taken. It is named here and not 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. 5 The property the tree was built for, which is unrelated to any of the work above: the entropy of the document array, decided by the collection’s length distribution.

A crossing that is not a number

One more consequence of the shrinking constant deserves stating, because it changes what a crossing is.

With two methods whose costs are a·range and c·answer, the crossing is a single ratio: the method wins when range/answer exceeds c/a. One number, quotable, and the same on every collection.

With the descent the comparison is range against 2k(1 + log₂(d/k)), which involves d as well as the ratio. So the crossing depends on the collection’s document count and on where in the collection the answer sits, and there is no single ratio to quote.

Working it out on this sweep: at d = 32 the descent wins when the range exceeds roughly 2k(1 + log₂(32/k)), which at k = 8 is 56 rows and at k = 32 is 64. So the crossing in rows barely moves while the crossing in occurrences per document falls steeply — from seven at a small answer to two at a full one.

That is why this strand’s crossings have been quoted three different ways in three essays and none of them was wrong. The published apparatus had a constant, so 10.8 occurrences a document was the right form. The descent does not, so a single density is the wrong form and the honest statement is the formula plus the collection’s d.

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. 6 The other collection property that enters the descent’s cost through the tree’s shape: how the characters are apportioned, which sets the code lengths and therefore the depths.

Which order the leaves come in

Early stopping raises a question the full-answer case does not, and it is worth flagging because it turns a small ordering issue into a correctness one.

The descent’s leaf order is the tree’s, which on a frequency-shaped tree is frequency order rather than document order. Stopping after ten therefore reports the ten documents whose codes come first, which is an arbitrary subset — not the ten smallest document numbers, not the ten with the most occurrences, and not any ordering a reader would expect.

For a full answer that does not matter, because the set is the same and sorting is cheap. For a truncated answer it matters completely: the ten reported are decided by the code shape, which is decided by the collection’s length distribution, which has nothing to do with relevance.

So a system doing early stopping needs either a balanced tree, which gives document order and costs the compression, or a ranking criterion applied to the whole answer, which defeats the point. The smaller tree hands it back unsorted prices the first of those, and the second is why real search systems do not truncate a document list this way.

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. 7 The shape decision that early stopping turns from a preference into a correctness question: two trees returning the same set in different orders.

Three shapes a cost can have

It is worth collecting the shapes this apparatus has produced, because a reader choosing between them is choosing between shapes rather than between numbers.

Proportional to the input. Reading every occurrence costs the range. It is the cheapest method when the range is small and it degrades without limit: a pattern occurring a million times in three documents costs a million operations to report three.

Proportional to the answer, with a large constant. The range-minimum walk. Its cost stops following the range, which is the property being bought, and the price is a constant of about twenty-seven operations a document — enough that on every collection in this field the property is not worth what it costs.

Proportional to the answer, with a shrinking constant. The descent. Its cost is 2k(1 + log₂(d/k)), so it is between six and two operations a document depending on how much of the collection is in the answer, and it is the only one of the three whose per-unit cost improves as the work grows.

The third shape is the one worth having and it is also the one that is hardest to summarise. A method whose per-unit cost is 5.14 in one regime and 1.94 in another cannot be quoted with a single constant, and the temptation is to quote the average — which is the statistic distributions, not averages exists to warn about, and which here would hide exactly the feature that makes the method good.

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. 8 A quantity the query cost does not depend on and the structure’s size does: what a plain-vector tree costs against the entropy, and the floor a code cannot get under.

The right summary is the formula, and the formula is short enough to carry: two ranks per shared ancestor, and the shared ancestors of k leaves out of d are k(1 + log(d/k)). Everything above follows from that and from nothing else about the structure.

What the measurement had to be careful about

The sweep above holds one thing fixed that is easy to let move, and getting it wrong produces a plate that says nothing.

The answer’s size is varied by planting the pattern in a controlled number of documents. The occurrences per holding document is held at the planting rate. So the range grows because the answer grows, and the ratio between them stays roughly constant — which is what makes “work against answer” a meaningful axis.

The alternative — varying the pattern’s length, which changes the range — moves both the answer and the density at once, and the resulting plate cannot separate a method that follows the answer from one that follows the range. That is the same design error a boundary that costs nothing records for a different sweep: the axis has to be the thing being varied and nothing else.

The check that the sweep is measuring what it claims is that the per-document cost of the descent stays inside a band while the range moves by a factor of nineteen. It moves by 2.65, which is the ancestor-sharing effect and is predicted; if it moved by nineteen the method would be following the range and the label would be wrong.

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.

Crossing pointDescentDocument arrayDocument listingLower boundOutput-sensitiveRange minimumWavelet tree