What a bound is

Two at binary, five at twenty-six

The saving is a factor in the alphabet, so a two-symbol alphabet gets two. Approximate matching in this field is mostly done on DNA, which sits near the bottom of the list at 2.7.

Every saving in this strand is a factor in σ, and a factor in σ is a small number when σ is small.

Four alphabets, one interval of sixty-four positions, one measurement.

Binary, two symbols: the loop costs four ranks and the descent costs two. A factor of 2.0.

DNA, four symbols: sixteen against six. A factor of 2.7.

Protein, twenty symbols: two hundred against forty-two. A factor of 4.8.

Latin, twenty-six letters: two hundred and sixty against fifty-four. A factor of 4.8.

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. 1 The two costs on four alphabet sizes over the same interval width. The saving is a factor in sigma, and the interval here is wide enough to hold nearly every symbol.

Why the ordering matters more than the numbers

Approximate string matching over a compressed index is done, overwhelmingly, on DNA. That is where the read aligners are, where the index sizes matter, and where the error budgets are. Protein search is the second-largest use and natural-language search is a distant third.

So the ordering above is exactly backwards from where the work is. The operation is worth 4.8 on the alphabet nobody uses it on and 2.7 on the one everybody does.

That is not a reason to dismiss it — a factor of 2.7 on a search costing tens of millions of operations is a real saving — and it is a reason to be careful about which number gets quoted. A factor measured on English is about four times what a genomics workload will see.

The binary row is worth pausing on because it is the degenerate case and it says what the operation is. On two symbols the loop asks two questions and the descent enters two nodes, so both do the same work in a different order. There is no saving because there is nothing to prune: with two symbols there is nothing that can be absent without the interval being empty of one of them, which the loop discovers in one question.

That the operation degenerates rather than breaking is the right behaviour, and it is worth checking rather than assuming. A version that was somehow worse on binary would indicate an overhead in the descent’s bookkeeping that the node count is not charging for.

Where the factor comes from

The arithmetic is short enough to write and it explains both ends.

A loop asks about each of σ symbols, and a rank walk on a balanced tree over σ symbols is ⌈log₂ σ⌉ levels at two ranks a level in this accounting. So the loop costs 2σ⌈log₂ σ⌉.

The descent, on an interval holding every symbol, enters the whole tree: 2σ − 1 nodes, of which σ − 1 are internal, at two ranks each. So it costs about 2σ.

The ratio is ⌈log₂ σ⌉, halved by nothing — the two ranks a node appear on both sides. At σ = 2 that is one; at σ = 4 it is two; at σ = 20 it is five; at σ = 26 it is five.

The measured figures are 2.0, 2.7, 4.8 and 4.8, which sit slightly under those because not every symbol is present in a sixty-four position interval on the larger alphabets — twenty-three of twenty-six on Latin, nineteen of twenty on protein.

So the worst case factor is ⌈log₂ σ⌉ and the sweep above is close to it. The best case, with one symbol present, is σ.

There is an assumption buried in that arithmetic and it is the balanced tree. On a frequency-shaped tree the loop’s cost is Σ over symbols of the code length rather than σ log σ, which is n·H₀-like and smaller — so the loop is cheaper on a skewed alphabet and the factor falls.

The descent’s cost falls too, because a shaped tree is shallower for frequent symbols and a narrow interval usually holds frequent ones. Which effect is larger is not obvious and this strand does not measure it, because every measurement here uses a balanced tree for the reason proportional to the answer, not the alphabet gives: a shaped tree makes the loop’s line depend on the text, and the comparison stops being about the descent.

So the factors here are for a balanced tree, which is the shape a search index over a small alphabet actually uses — on four symbols there is nothing to shape.

Two bounds, and the range between them

That gives the operation’s whole envelope in two lines.

Best case, one symbol present: the descent walks one root-to-leaf path, so the factor is σ.

Worst case, every symbol present: the descent walks the tree, so the factor is ⌈log₂ σ⌉.

At σ = 4 that is a range of four to two. At σ = 26 it is twenty-six to five. At σ = 256 — a byte alphabet — it is two hundred and fifty-six to eight.

The range is enormous on a large alphabet and almost nonexistent on a small one, which is the sharper version of the ordering above. On DNA the operation’s best and worst cases differ by a factor of two, so there is nothing to be gained by knowing which regime a query is in.

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. 2 The range on a thirty-two symbol alphabet, drawn: the factor falls from thirty-two to five as the interval fills up.

What a search does, which is not the census

The census numbers above are one interval at one width. A search is thousands of intervals of varying widths, and the aggregate factor is not the average of the census factors.

Measured on a backtracking search for eight-character patterns within one error: DNA gives 2.8 and protein gives 7.0.

Protein’s 7.0 is above its census worst case of 4.8 and below its best case of 20. That is because a search spends most of its time in narrow intervals, where few symbols are present, and the aggregate is weighted toward the good end.

DNA’s 2.8 is barely above its census figure of 2.7, because its range is 4 to 2 and there is nowhere for the weighting to take it.

So the alphabet decides not only the factor but how much the query’s shape can improve it. On a large alphabet a search’s own behaviour buys extra; on a small one it does not.

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 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 19 symbols present. That is 40 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.adefghiklmnpqrstvwyroot19 of 20 symbols present42 ranks · 40 nodes
Fig. 3 The mechanism the alphabet scales: the nodes a descent enters over one interval, against the whole tree the loop pays for.

The other thing σ decides

There is a second effect of the alphabet that runs the other way and it is worth putting beside the first.

A larger alphabet makes a backtracking search more expensive in absolute terms, because each level branches over more characters. An eight-character search within one error on twenty symbols attempts 16,800 extensions; on four symbols it attempts far fewer.

So a large alphabet gives a large saving on a large cost and a small alphabet gives a small saving on a small cost. Whether the absolute saving is larger on protein than on DNA is a different question from whether the factor is, and the answer is yes by a wide margin: 144,000 ranks saved against 38,000.

The branches an error opens is where the branching factor of an approximate search was first measured here, and its numbers are on DNA for the reason given above — that is where the workloads are. Reading the two together: the alphabet where the search is cheapest is the alphabet where this operation helps least, and the two effects partly cancel.

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 4-symbol alphabet, 8 patterns. The bar is every extension the published shape attempted: 459 of 2,428 — 18.9% — 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 1,969 live ones, so the search visits the same nodes in the same order and costs 3,426 ranks against 9,712 — 2.83x. The two must find the same occurrences, and on every one of the 8 patterns they do.extensions attempted459 dead1,969 livebit-vector ranksthe loop: 9,712the descent: 3,4268 patterns · 1 error · sigma 418.9% dead · 2.83x
Fig. 4 The same search on DNA rather than protein: the extensions attempted, split into the ones that found an interval and the ones that found nothing.

The dead share, by alphabet

The mechanism the operation exploits is the dead branch, so its share by alphabet is the useful diagnostic.

On DNA at one error, 19% of extensions find nothing. On protein at one error, 55%.

That difference is the whole of why the factors differ. A four-symbol alphabet in a narrow interval usually has most of its symbols present — an interval of twenty rows over four symbols almost certainly holds all four — so there is little to prune. A twenty-symbol alphabet in the same interval holds maybe six, and fourteen extensions per node are wasted.

So the dead share is roughly 1 − E[d]/σ, and E[d] is bounded by the interval’s width. On a narrow interval and a large alphabet the share approaches one.

There is a third thing the alphabet decides and it is the one that limits how far any of this can be pushed. The number of symbols an interval can hold is bounded by its width. An interval of six rows holds at most six distinct symbols, whatever σ is.

So on a large alphabet the deep intervals of a search are automatically in the good regime — six symbols out of two hundred and fifty-six is a factor near the ceiling — and on a small alphabet they cannot be, because six rows over four symbols probably holds all four.

That bound is why the dead share and the alphabet are not independent, and it is why a single sweep over σ tells most of the story. The interval’s width and the alphabet interact through min(width, σ), and once the width is below σ the alphabet stops mattering to the descent while continuing to matter to the loop.

What this means for a claim

The general form is one this collection keeps arriving at and this is a clean instance.

A factor quoted without its alphabet is a factor about one workload. The operation’s saving spans 2.0 to 4.8 across four ordinary alphabets and 2.8 to 7.0 across the same four on a real search, and there is no single number.

That is the same shape as a sixth of what, exactly, where a saving quoted as “about a sixth” turned out to be 14.0% at one sampling rate and 30.5% at another. The dial there was a setting; here it is a property of the data.

What makes the alphabet version worse is that it is not a choice. A sampling rate can be tuned; an alphabet is what the data has. So a reader on DNA cannot get to protein’s factor by adjusting anything, and quoting protein’s factor to them is not merely imprecise but unactionable.

Reading the two sweeps together

Two axes have now been swept and they are not independent, so it is worth saying how a reader should combine them.

Proportional to the answer, not the alphabet fixes σ and moves d, producing a factor from σ down to log σ. This essay fixes d/σ roughly and moves σ, producing a factor from 2.0 to 4.8.

The single expression covering both is the ratio of the loop’s 2σ⌈log₂ σ⌉ to the descent’s 2d(1 + log₂(σ/d)), which is σ⌈log σ⌉ / (d + d log(σ/d)). Two parameters, one surface, and the two sweeps are two cuts through it.

The cut that matters for a workload is neither of them. It is the distribution of (d, σ) pairs a real search visits, weighted by how much work each costs — and that is what the search measurements report as a single aggregate factor. Protein’s 7.0 is that weighted aggregate, and it sits above the census figure because the weighting favours narrow intervals.

So the two sweeps explain the aggregate rather than predicting it, and the aggregate is the number a reader should use. That ordering — sweeps for the mechanism, aggregate for the decision — is the one this collection has settled on wherever a cost has two arguments.

Where the operation is worth most

Collecting the conditions under which the factor is large.

A large alphabet. The factor’s ceiling is σ and its floor is log σ, and both grow.

Narrow intervals. Which means a long pattern, a specific query, or a deep position in a search.

A high error budget. Which produces narrow intervals in quantity — the dead share rises from 31% to 75% between zero and two errors on protein.

And a search that branches at all. An exact search knows which symbol it wants, and enumerating every present symbol to hand back one of them costs about twice the walk. The saving that is a loss is that measurement.

Three of those four are properties of the query rather than of the data, which is the encouraging part: a reader on a small alphabet can still reach the good end of the range by asking harder questions, and asking harder questions is what the operation exists to make affordable.

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. 5 The dial that moves the dead share, on the alphabet where it moves most: the saving against the error budget, with the share of dead extensions at each.

The alphabet that is not the alphabet

There is a way to change σ without changing the data, and it is worth naming because it turns this essay’s ordering from a fact into a design choice.

Index pairs of characters rather than characters. A DNA text over four symbols becomes a text over sixteen, of half the length; over triples it is sixty-four symbols and a third the length. The searches are then over a coarser alphabet with a finer factor, and this operation’s saving rises from 2.7 toward 4.8.

That is a real technique and it has costs this strand does not price: patterns must align to the grouping or be searched at every offset, the wavelet tree gets deeper per symbol, and the sampling and marks all change shape. It is named here because the alphabet appears in this essay as a fixed property of the data, and it is not quite one.

What it does not change is the ordering of the workloads. A grouped DNA alphabet is a design decision made for other reasons — mostly cache behaviour — and a search over it pays elsewhere for what it gains here.

The saving is a factor in sigma, so a two-symbol alphabet gets twoThe descent against the loop on an interval of 256 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 260256-position interval2.00x to 4.81x
Fig. 6 The same four alphabets at a wider interval, where more symbols are present and every factor moves toward its worst case.

The byte alphabet nobody measured

There is a case at the far end of the range that this strand does not measure and that is worth naming, because it is where a general-purpose implementation lives.

A byte alphabet — 256 symbols — has a factor of 256 in the best case and 8 in the worst. Real text over bytes uses maybe eighty of them and a narrow interval holds a handful, so a search on it would sit far toward the good end.

That is not measured here for a reason: the texts this collection generates are over the alphabets named above, and building a byte-alphabet corpus that is representative of anything would be building a corpus rather than an instrument. The prediction from the arithmetic is a factor in the tens, and it is a prediction.

The one thing worth saying about it without a measurement is that the loop on a byte alphabet is 256 rank walks a node, which is an implementation nobody ships — real byte-alphabet searches use a different structure entirely. So the comparison would be against something that does not exist, and a factor against a straw implementation is not a factor.

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 20-symbol alphabet. The loop is flat at 200. The descent starts at 18 ranks for 3 symbols and stops rising at 42, because once the interval holds every symbol there is nothing further to enter — so the saving is bounded below by 4.76x 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 = 3d = 12d = 19d = 20d = 20the loopthe descentsigma = 2011x narrow, 4.76x wide
Fig. 7 Where the interval width puts a query on the range, on the alphabet the second-largest share of this work is done over.

What the small alphabet gets instead

Ending on “DNA gets 2.8” would be a fair reading of this essay and an incomplete one, because the same field has other savings whose alphabet dependence runs the other way.

The half that is never asked where drops a bidirectional index’s reverse locating apparatus, and that saving is a share of the structure — it does not depend on σ at all. The array that says where is twice the samples replaces a mark vector with a sparse representation, and that saving depends on the sampling rate rather than the alphabet.

So a DNA workload getting 2.8 from this operation is not a DNA workload that has been left out of the field’s improvements. It is one whose improvements come from elsewhere, and the strand that measures three savings together — three savings on one structure — is where the composition is worked out.

The general point is that a strand reporting a σ-dependent factor should say which of the field’s other savings are not σ-dependent, so that a reader on a small alphabet knows whether they have been served or skipped. Here they have been served, by different machinery.

One cost follows the alphabet and the other follows the answerA 8-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 48 ranks: it asks about every symbol whether or not the symbol is there. The descent rises from 6 to 14 — a factor of 8.00x down to 3.43x — 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 3.43x, which is the two-ended shape of the claim.020402468distinct symbols in the intervalbit-vector ranksthe loop: 48the descentsigma = 8 throughout8.00x down to 3.43x
Fig. 8 The controlled sweep on an eight-symbol alphabet, where the whole range from best case to worst is a factor of eight down to three.

What to quote

Two numbers and a condition.

On DNA, 2.8 on an approximate search. That is the workload most of this field is about and it is the smallest of the factors.

On protein, 7.0 at the same budget. That is the second-largest workload.

And the condition: the search has to branch. Everything above is about a query that extends by more than one symbol, which is an approximate search or a wildcard or a regular expression. An exact search gets nothing from this operation and pays about twice for it.

Anything else — a factor from a census, a factor from natural language, a factor with no alphabet attached — is a number about a different measurement.

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.

Alphabet sizeBacktracking searchCrossing pointDescentInterval symbolsRankWavelet tree