What is taught wrongly

One set, three orders

The symbols an interval holds do not depend on the tree's shape. The order they come out in does, and a search that accumulates a running count as it reads them computes a plausible number that is wrong on eighty per cent of queries.

A wavelet tree’s leaves are its code words read as paths. A descent reports symbols in the order it reaches their leaves, which is left to right, which is the order the code words sort as strings.

That gives two claims about the enumeration and they have to be kept apart, because one of them is a fact about intervals and the other is a fact about codes.

The set does not depend on the shape. Which symbols an interval holds is a property of the interval. Any tree over the same alphabet, however shaped, reports the same ones.

The order does. A balanced code assigns consecutive code words to consecutive symbols, so its leaves are in the alphabet’s order. A Huffman code assigns short words to frequent symbols wherever they fall, so its leaves are in frequency order.

The same symbols, in three different ordersOne interval of 2,048 positions, enumerated on three tree shapes over the same text. All three return the same 21 symbols — the set is a property of the interval and not of the tree. The ORDER is not: a balanced tree and an alphabetic one put their leaves in the alphabet's order, so the symbols come out sorted, and a Huffman tree puts them in frequency order, so they do not. A caller that only wants to know which symbols are present may use any shape. A caller that accumulates "how many characters sort before this one" as it reads the output — which is exactly what a bidirectional extension does — may use only the first two, and reading a Huffman tree's output as sorted produces a plausible number that is wrong.a fixed-length codeleaves in orderacdefghilmnoprstuvwythe best ordered treeleaves in orderacdefghilmnoprstuvwythe best tree of any shapeleaves in frequency ordereahnrstdiloucfgmpvwythe symbols, in the order the descent reports them21 symbols in 2,048 positionsone set, 2 of 3 sorted
Fig. 1 One interval enumerated on three tree shapes over the same text. All three return the same twenty-one symbols; two of them return them sorted.

What the two claims need separately

The set claim is checked by running the descent on three shapes over the same text and requiring the sets to be equal — twenty-four intervals across three shapes, every set identical.

The order claim is checked by requiring the shapes to disagree. A balanced tree’s output must be sorted and a Huffman tree’s must not, and a check that only asked for the first would pass on a Huffman tree that happened to be ordered by accident on a small alphabet.

That second half is the part that makes this a two-ended check rather than a claim. On a uniform alphabet all three shapes are the same tree, so a check run there would report agreement and prove nothing. The measurement uses English-like text over twenty-one symbols, where the frequencies are skewed enough that the Huffman code’s order differs from the second symbol onward.

There is a third shape in the picture and it is the one that resolves the tension rather than illustrating it. An alphabetic code is the minimum-cost prefix code among those whose leaves are in order — so it is shaped by the frequencies like Huffman’s and ordered like a balanced one. Its output is sorted and it costs a few per cent more than Huffman.

Drawing all three makes the check’s two-endedness visible: two shapes ordered, one not, and one set among all three. A plate showing only two would leave a reader unsure whether the order is a property of frequency-shaping in general or of Huffman’s construction in particular. It is the latter, and the third row is the evidence.

What a caller does with the order

For a caller that wants a set, none of this matters. Document listing wants a set: which documents hold this pattern, in any order, and the tree answers the question is that use.

For a caller that reads the output in sequence, accumulating something as it goes, it matters completely.

The case in hand is a bidirectional search. Extending an interval by a character at one end requires knowing, at the other end, how many characters sort before that one inside the interval — because in the mirrored index those extensions sit before this one and the interval’s start has to move past them.

The enumeration gives that for free: read the symbols in order, keep a running total of their counts, and the total before symbol c is exactly the smaller-count. One descent, every symbol’s smaller-count, no extra work.

Provided the order is the alphabet’s.

The descent enters only the ancestors of the symbols that are thereA wavelet tree over 4,096 characters of a 20-symbol alphabet, with one interval of 48 positions descended. The dark path is what the walk entered: a child is entered only if its half of the interval is non-empty, so the nodes visited are exactly the ancestors of the 18 symbols present. That is 39 nodes and 42 bit-vector ranks against the 100 a loop over the alphabet costs. The pale nodes are the rest of the tree, which the loop pays for and this walk never touches. Each node costs two ranks and not four, because the left child's bounds are the position minus the right child's.adefghiklmnprstvwyroot18 of 20 symbols present42 ranks · 39 nodes
Fig. 2 Where the order comes from: the descent enters nodes left to right, so the leaves are reached in the order their code words sort as strings.

It is worth seeing why the running sum works at all, because it is not obvious that a descent produces the counts in a usable order.

The descent visits a node’s left subtree before its right, and every leaf in the left subtree has a code word beginning with zero while every leaf in the right begins with one. So the leaves come out in code-word order, and if the code preserves the alphabet’s order then they come out in the alphabet’s order.

The counts arrive with them — each leaf’s interval width is the count of that symbol in the range — so a running sum over the sequence is a running sum over the alphabet, which is the smaller-count.

Nothing about this required the tree to be balanced. It required the code to be order-preserving, which is a different property that a balanced code happens to have.

What goes wrong when it is not

On a Huffman tree the running total is a running total of the frequency order, which is a plausible number with no meaning.

Measured on a twenty-one symbol alphabet over eight thousand characters: twenty of the twenty-one running totals are wrong, and the worst is out by 1,798 on an interval of two thousand.

Every count is a non-negative integer less than the interval’s width. Every one of them is the correct count of something — the characters preceding this one in code order. Nothing about the number looks wrong, and a search using it produces intervals that are the right width and in the wrong place.

That is the failure mode this collection has met before in the same shape. The tree the operation insists on found the compound walk producing a smaller-count wrong on 81% of queries when its tree was Huffman-shaped, worst 706 against 3,058 — the same defect, one operation earlier, on the same property.

The number 1,798 is worth a sentence because its size is the point. On an interval of two thousand positions, a smaller-count that should be a few hundred comes back as a few thousand or the reverse — the error is comparable to the interval itself.

That means the resulting interval is not merely displaced by a little; it is somewhere else entirely in the index. A search using it would report occurrences at positions that have nothing to do with the pattern, and would report the right number of them, because the interval’s width comes from the forward half and is correct.

The right count of wrong positions is the worst possible failure shape. A count that is wrong is caught by a test on the count; positions that are wrong are caught by a test on the positions; and this collection has tests of both kinds. What it caught this with was neither — it was a check comparing two tree shapes’ outputs directly, which exists because the shapes were suspected rather than because a symptom appeared.

Why it is caught here and not there

The two instances differ in how they are prevented and the difference is instructive.

The compound walk requires an ordered tree to be correct at all, so the index that uses it refuses a Huffman shape in its constructor. An unordered tree cannot be handed to it.

The enumeration does not require one. Its set is correct on any shape, and only the sequential reading needs the order. So refusing an unordered tree would forbid a use that is perfectly valid — the document listing above, which uses a Huffman tree deliberately for its size.

That leaves the requirement in the caller rather than in the structure, which is a weaker place for it. A structure can enforce its own precondition; a caller has to be written correctly, and nothing checks that it was.

The descent enters only the ancestors of the symbols that are thereA wavelet tree over 4,096 characters of a 26-symbol alphabet, with one interval of 64 positions descended. The dark path is what the walk entered: a child is entered only if its half of the interval is non-empty, so the nodes visited are exactly the ancestors of the 24 symbols present. That is 51 nodes and 54 bit-vector ranks against the 130 a loop over the alphabet costs. The pale nodes are the rest of the tree, which the loop pays for and this walk never touches. Each node costs two ranks and not four, because the left child's bounds are the position minus the right child's.acdefghijklmnopqrstuvwyzroot24 of 26 symbols present54 ranks · 51 nodes
Fig. 3 The descent that produces the order: the nodes entered over one interval, where the leaves reached are reported left to right.

Where the requirement is written down

The resolution this strand adopted is to put the constraint where the composition happens.

The bidirectional index that branches by enumeration checks its trees’ shape in its constructor and refuses an unordered one — the same guard the compound index has, for the same reason, on a different operation. So the index that reads the output sequentially will not build on a shape that would make the reading wrong.

The operation has no such guard, and is used unguarded by the document listing.

That splits the responsibility correctly: the structure that has a precondition enforces it, and the operation that does not have one does not impose it. What it leaves is a third party — some future caller reading the enumeration’s output in sequence without going through the bidirectional index — that nothing protects.

The only instrument available for that is a rejection test that demonstrates the failure, so that a reader who finds the operation and reaches for a running sum has been told. That test is in the strand: feed the enumeration a Huffman tree, take the running sums, compare against the truth, and require them to differ.

The check that would have been vacuous

Two versions of the order check were written and the first one could not fail, which is worth recording because the reason is subtle.

The first version compared a balanced tree’s output against a Huffman tree’s and required them to differ. Run on a uniform alphabet — four symbols, equal frequencies — the two trees are the same tree, so the outputs are identical and the check reports that the shapes agree. Which is true, and says nothing about the claim.

The repair is not a different comparison but a different input: the check has to be run on a text whose frequencies are skewed enough that Huffman’s code is genuinely not the balanced one, and it has to verify that the two differ before concluding anything from the fact that they do.

So the check has three parts rather than two. The sets must agree; the orders must differ; and the shapes must actually be different shapes, which on a uniform alphabet they are not.

That third part is the one this collection keeps having to add. The tree the operation insists on records the same repair on the compound walk: an assertion that “the best ordered tree is ordered and Huffman’s is not” fails on a uniform alphabet, where all three shapes are the same tree — which is the case the essay is about. Split into two claims, the second conditional on the sizes differing.

The saving is a factor in sigma, so a two-symbol alphabet gets twoThe descent against the loop on an interval of 64 positions, across four alphabets. On binary text the loop asks two questions and the descent enters two nodes, so the whole saving is 2.00x and there is nothing to report. On a twenty-six letter alphabet the loop costs 260 ranks against 54 — 4.81x. That ordering matters for what this operation is for: DNA search, which is where most published approximate matching happens, sits near the bottom of this chart at 2.67x, and protein and natural language sit near the top.binary · sigma 22.00x2 against 4dna · sigma 42.67x6 against 16protein · sigma 204.76x42 against 200latin · sigma 264.81x54 against 26064-position interval2.00x to 4.81x
Fig. 4 The alphabets the check has to be run on and the ones it cannot: on two symbols every shape is the same tree, so the order claim has nothing to say.

The general form

Stated without wavelet trees, this is a property that a representation gives up for a size and a caller relies on without saying so.

A Huffman code loses the alphabet’s order. A hash table loses the key order. A frequency-sorted posting list loses the document order. A compressed bit vector loses the ability to read a range of bits directly. In each case the structure is correct and something built on it silently is not.

The pattern is hard to catch because the property is rarely written down. Nobody specifies that an enumeration returns its symbols sorted, because the obvious implementation over a balanced tree does, and a caller written against that implementation depends on it without noticing.

The instruction is a writing rule rather than a checking one: when a representation is chosen for its size, write down which properties it gave up, in the place a caller will read. Here that is a sentence in the operation’s own description, and the reason it took a rejection test to arrive at is that the property was not missing from the implementation — it was missing from the account.

One cost follows the alphabet and the other follows the answerA 32-symbol alphabet throughout, with the measured stretch drawn from a restricted sub-alphabet so that the number of distinct symbols moves and sigma does not. The loop is flat at 320 ranks: it asks about every symbol whether or not the symbol is there. The descent rises from 10 to 62 — a factor of 32x down to 5.16x — and rises like d log(sigma/d) rather than like d, because the ancestors of few leaves overlap near the root and the ancestors of many do not. At d = sigma the walk enters every node and the two are within 5.16x, which is the two-ended shape of the claim.0100200300102030distinct symbols in the intervalbit-vector ranksthe loop: 320the descentsigma = 32 throughout32x down to 5.16x
Fig. 5 The measurement the order says nothing about: the descent’s cost against how many symbols are present, which is the same on every shape.

Why the set is shape-free

The set claim gets less attention than the order claim and it deserves a paragraph, because it is what makes the operation usable at all and it is not obvious.

A descent enters a child when that child’s half of the interval is non-empty. Which positions are in which half depends entirely on the code — a different code sends different symbols left — so two shapes send the interval down completely different paths.

What they agree about is the leaves. A symbol is present in the interval if and only if at least one position of the interval carries it, and that is a fact about the interval and the sequence rather than about the tree. Every path from the root to that symbol’s leaf therefore has a non-empty interval at every step, whichever path it is, so the descent reaches the leaf on any shape.

The converse is the same argument backwards: a leaf reached has a non-empty interval, and a non-empty interval at a leaf means at least one position carrying that symbol.

So the set is the interval’s alphabet and the tree is a way of enumerating it. Three shapes, three sets of intervals along the way, one answer.

A wider interval holds more symbols, and then stopsThe two ways of enumerating an interval's symbols, against the interval's width, on 8,192 characters of a 26-symbol alphabet. The loop is flat at 260. The descent starts at 24 ranks for 4 symbols and stops rising at 54, because once the interval holds every symbol there is nothing further to enter — so the saving is bounded below by 4.81x and reaches 11x on the narrow intervals a search actually spends its time in. Both return the same set at every width, which is what makes this a plate about cost.1010010³100positions in the intervalbit-vector ranksd = 4d = 14d = 22d = 26d = 26d = 26the loopthe descentsigma = 2611x narrow, 4.81x wide
Fig. 6 The quantity that is the same on every shape, measured against the interval’s width: how many distinct symbols an interval holds.

What the shapes cost each other

Since the order is a property of the shape and the shape has a size, it is worth putting the two together once.

On a text where the symbol frequencies are skewed, a Huffman tree’s payload is n·H₀ and a balanced one’s is n⌈log₂ σ⌉. On English-like text over twenty-one symbols that is about 4.2 bits a symbol against 5, a saving of sixteen per cent.

An alphabetic code — the minimum-cost prefix code among those preserving order — gets most of that saving and keeps the order. A factor of fourteen, for four per cent measured it in another strand: 1.43% over Huffman on generated English, 3.5–5.0% on real text, and the same mean depth.

So the trade is not size against order; it is size against order against construction complexity, and the third option gets both for an interval dynamic program instead of a merge.

That is the right answer wherever the order is needed, and it is the answer neither of this strand’s two uses takes: the search uses a balanced tree because it is over DNA and there is nothing to shape, and the listing uses a Huffman tree because it does not need the order.

What a sorted answer is worth elsewhere

The order matters to one more caller and it is the one a real system is most likely to be, so it belongs beside the search.

A document listing that reports a truncated answer — the first ten of four hundred — chooses which ten by the order the descent produces. On a Huffman tree that is the ten documents with the shortest codes, which is the ten largest documents that happen to hold the pattern. That is a biased sample with a bias nobody chose, and the smaller tree hands it back unsorted is where it is priced.

So the same property that makes a search wrong makes a truncated listing arbitrary. The difference is that the search’s failure is loud once anybody checks a position and the listing’s is silent forever, because ten correct documents are indistinguishable from ten other correct documents.

That gives three uses of the enumeration with three different relationships to the order: a full listing does not care, a truncated listing cares and cannot tell, and a sequential reader is wrong. Only the third is guarded by anything.

The looser the budget, the more of the search is wastedThe share of extensions that find nothing, against the error budget, for 8-character patterns over 8,192 characters. At zero errors the search follows one path and the dead share is 31.3%; at 2 it is 74.9%, because the intervals a two-error search reaches are narrow and a narrow interval holds few of the alphabet's symbols. The saving follows: 5.75x at no errors and 9.22x at 2. That is the opposite of what a reader might expect from "more work means more to save on" — the work grows and the FRACTION that was never going to help grows with it.02040608000.50011.502errors permittedbranches that find nothing, per cent5.75x6.98x9.22xthe label is thesaving at that budget8 patterns · 8 characters31.3% to 74.9%
Fig. 7 The use where the order is guarded: an approximate search’s cost against the error budget, on an index whose constructor refuses an unordered tree.

Three shapes, three uses

The strand ends with a clean division and it is worth stating because it explains why all three shapes are in the code.

Balanced where the alphabet is small or uniform, and where the order is needed. The search’s index.

Huffman where the sequence is skewed and only the set is needed. The document array.

Alphabetic where both — skewed and ordered — which is where neither of the two uses landed and which is the shape a third use would want.

Every child at once is where the compound walk’s own shape requirement was worked out, a factor of fourteen, for four per cent is where the alphabetic code was priced, and the array is the length distribution is where the Huffman shape earns its place on a sequence that needs no order.

Most of what a branching search does is find out that nothing is thereAn approximate search for 8-character patterns within 1 error, over 8,192 characters of a 26-symbol alphabet, 8 patterns. The bar is every extension the published shape attempted: 11,126 of 20,696 — 53.8% — produced an empty interval, which is a full rank walk down the wavelet tree whose entire result is the discovery that the character was not there. Enumerating the interval's symbols removes exactly those and keeps the 9,570 live ones, so the search visits the same nodes in the same order and costs 29,712 ranks against 206,960 — 6.97x. The two must find the same occurrences, and on every one of the 8 patterns they do.extensions attempted11,126 dead9,570 livebit-vector ranksthe loop: 206,960the descent: 29,7128 patterns · 1 error · sigma 2653.8% dead · 6.97x
Fig. 8 The use whose index refuses an unordered tree: an approximate search’s extensions, split into the ones that found an interval and the ones that found nothing.

Three shapes, one operation, and the choice decided by a property of the caller rather than of the data. That is the unusual part: every other representation decision in this collection was decided by what was being represented. A compressed bit vector is chosen because the sequence has runs; a sampling rate is chosen because the text has a length; a code shape is chosen because the symbols have frequencies. Here two of the three shapes are chosen for the frequencies and which of the two depends on what somebody downstream is going to do with the answer, which is information the structure does not have and cannot measure.

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.

Bidirectional indexCheckCorrectnessDescentHuffman codeInterval symbolsOrdered codeWavelet tree