The data that is not a number

The q-grams an error cannot destroy

A pattern of twenty-four characters holds twenty-one four-grams. Two errors can destroy at most eight of them, so any occurrence with two errors still shares thirteen — and a filter that keeps only the windows sharing thirteen proposes 104 of 3,977 and computes 16,744 table cells instead of 96,000.

A pattern of mm characters holds mq+1m - q + 1 overlapping substrings of length qq. One error — a substitution, an insertion, a deletion — sits inside at most qq of them, because a changed character belongs to qq windows of that length.

So an occurrence with at most kk errors still shares at least

t=(mq+1)kq=m+1q(k+1)t = (m - q + 1) - kq = m + 1 - q(k+1)

of them with the pattern. That is the whole of the filter: slide a window along the text, count the shared qq-grams, and verify only the windows that reach tt.

How many q-grams an occurrence must still share, m = 24Each cell is t = m + 1 − q(k+1): the number of q-grams a window must share with the pattern before it is worth verifying. The shaded cells are where t has reached zero or below, which is a demand of nothing — the filter proposes every window and has stopped being a filter. Nothing here is measured: the collapse is arithmetic, it is known before a text is read, and it happens at q(k+1) = m + 1.k = 0k = 1k = 2k = 3k = 4k = 5k = 6q = 223211917151311q = 3221916131074q = 4211713951-3q = 520151050-5-10q = 6191371-5-11-17q = 81791-7-15-23-31a shaded cell is a threshold of zero or less: every window proposedt = m + 1 − q(k+1) · m = 2411 collapsed cells
Fig. 1 The threshold across the whole design space at m = 24. Nothing here is measured — the number in each cell is arithmetic, and the shaded cells are where the demand has fallen to nothing.

The other filter, and why this is not it

This collection already has an approximate-matching filter. The filter that feeds the table cuts the pattern into k+1k+1 pieces and looks each up exactly, on the pigeonhole grounds that kk errors cannot damage all k+1k+1 of them.

Both filters rest on a pigeonhole argument and they are otherwise opposites.

The seed filter asks an index an exact question and gets back a set of positions. It never reads the text except to verify, and it skips: on the sweep below it touches four per cent of the characters.

The counting filter asks nothing. It reads every character exactly once, maintains a count as a window slides, and decides each window on a number. It cannot skip and it has no index behind it.

That difference is not an implementation choice. A filter that decides on a count has to have counted, and counting a window’s contents means reading them.

Two filters, one question, two currenciesA pattern of 24 characters against 4,000, answered identically by both filters at every k — checked against the whole table rather than assumed. The bar is cells computed in the verification stage; the number beside it is characters read. The counting filter reads every character and the seed filter reads 41.6% of them, and whichever of the two is the scarce resource decides which filter is better. The table's own cost is 96,000 cells and 96,000 reads at every row.k = 0: q-grams42016,132 readsk = 0: seeds420144 readsk = 1: q-grams5,33617,138 readsk = 1: seeds2,088450 readsk = 2: q-grams16,74418,692 readsk = 2: seeds4,830780 readsk = 3: q-grams27,67519,633 readsk = 3: seeds14,0381,834 readsk = 4: q-grams105,12227,850 readsk = 4: seeds160,44618,091 readsq = 4 · cells drawn · reads besidecells computed in verificationtable: 96,000 cells
Fig. 2 The two filters on the same question at five error counts, with cells drawn and characters read beside them. One of the two columns is flat at the length of the text.

What it costs, in the currency it saves

The expensive stage in approximate matching is the dynamic-programming table: n×mn \times m cells to find every place a pattern occurs with at most kk errors, which a distance that is a path through a grid built, the row that starts at zero turned into a search, and a band as wide as the answer narrowed to a band of width 2k+12k+1.

A filter’s job is to avoid filling it. On four thousand characters with a twenty-four-character pattern and six planted occurrences, the whole table is 96,000 cells; the counting filter at q=4q = 4 computes 420 of them at zero errors, 5,336 at one, 16,744 at two, 27,675 at three and 105,122 at four.

The last of those is larger than the table, which is the same shape the filter that proposes everything measured for the other filter. That is not a rounding error — it is the collapse, and it has its own essay in the threshold that reaches zero.

What the filter saves, until it does notA pattern of 24 characters in 4,000 characters of four symbols, with 6 occurrences planted at the stated error count. Each line is one q. The value is the whole table's cells divided by the filter's, so one is break-even. Every line reaches it inside this sweep, and the q that reaches it last is not the largest: a longer q-gram raises the threshold and lowers the chance of sharing one, and the two effects trade. The vertical axis is logarithmic.110100errors allowed, ktable cells / filter cells01234q = 3q = 4q = 5a shaded dot is a collapsed thresholdm = 24 · n = 4,000 · 6 planted1 collapsed rows
Fig. 3 The table’s cells divided by the filter’s, for three values of q. One is break-even and every line reaches it.

The window is m+km+k and not mm

One detail decides whether the filter is correct, and the natural choice is the wrong one.

An occurrence of an mm-character pattern with kk errors is between mkm-k and m+km+k characters long, because insertions lengthen it and deletions shorten it. A window of exactly mm characters may therefore hold only part of one, and the lemma bounds the shared qq-grams between the pattern and the whole occurrence.

So the window here is m+km+k characters starting at each position. Any occurrence beginning at that position is inside it, and a superset can only share more qq-grams, so the bound still holds.

This is the kind of argument that is easy to make and easy to make wrongly, so it is checked rather than trusted: at four error counts, every window the table reports an occurrence in must be a window the filter kept. On 2,500 characters with five planted occurrences, it is.

Multiplicity matters, and it is easy to drop

The count is over qq-grams with multiplicity: if the pattern holds three copies of the then a window holding five of them shares three, not one and not five.

Dropping the multiplicity is the natural simplification — a set is easier than a multiset and the code is shorter — and it breaks the lemma in the unsafe direction. The bound counts occurrences: a pattern of twenty-four characters holds twenty-one four-grams whether or not they are distinct, and each error destroys at most qq of them.

The gate’s check for this is a small one: on abababab with q=2q = 2 the profile must hold seven qq-grams over two distinct values. A set-based profile holds two, which is smaller than any threshold the arithmetic produces and would make the filter propose nothing at all.

The share of the text the counting filter proposesA pattern of 24 characters in 4,000 characters of four symbols, with 6 occurrences planted at the stated error count. Each line is one q. The share rises with the errors allowed because the threshold falls with them, and it reaches one exactly where q(k+1) = m + 1 rather than gradually. The vertical axis is logarithmic.0.010.11errors allowed, kshare of windows proposed01234q = 3q = 4q = 5a shaded dot is a collapsed thresholdm = 24 · n = 4,000 · 6 planted1 collapsed rows
Fig. 4 The share of windows proposed as the errors allowed grow, for three q. The rise is the threshold falling, and the last point of each line is where it reaches zero.

The count is maintained rather than recomputed

The filter as described sounds quadratic — count the shared qq-grams in every window — and it is not, which is worth a paragraph because the cost model in every plate depends on it.

The windows overlap by all but one position, so the count is maintained incrementally: as the window slides, one qq-gram leaves and one enters, and each of them raises or lowers the shared count by at most one. The bookkeeping is a table of how many copies of each qq-gram the window currently holds, compared against how many the pattern holds.

That is one hash-table operation per position rather than mm of them, and it is why the filter’s read count is nn plus qq rather than nn times ww. The characters are read once each and the counting is a constant per position.

The subtlety is the multiset arithmetic: a qq-gram entering raises the shared count only if the window now holds no more copies than the pattern does. Getting that condition backwards produces a count that drifts upward, a filter that proposes more than it should, and a completely correct answer set — the failure this collection’s gates exist to catch, since an over-proposing filter is safe and merely expensive.

Choosing qq, which is a trade rather than a maximum

The threshold falls by qq for every error allowed and rises by one for every character of qq taken away, so a larger qq gives a smaller threshold and a rarer qq-gram. Both effects matter and they point opposite ways.

At k=2k = 2 on this text: q=3q = 3 proposes 92 windows, q=4q = 4 proposes 104, q=5q = 5 proposes 121. The larger qq is worse here, because the threshold has fallen faster than the qq-grams have got rarer.

At k=0k = 0: q=3q = 3 proposes 8 and q=4q = 4 and q=5q = 5 propose 6, which is the planted count. The larger qq is better, because with no errors the threshold is nearly mm and any qq is rare enough.

So there is no best qq, and the arithmetic that decides it is available before any text is read: the threshold is exact, and the chance of sharing a qq-gram is about (mq+1)/σq(m-q+1)/\sigma^q. What is not available in advance is which of the two dominates on a real corpus, which is why the sweep exists.

The share of the text the counting filter proposesA pattern of 32 characters in 4,000 characters of four symbols, with 6 occurrences planted at the stated error count. Each line is one q. The share rises with the errors allowed because the threshold falls with them, and it reaches one exactly where q(k+1) = m + 1 rather than gradually. The vertical axis is logarithmic.0.010.1errors allowed, kshare of windows proposed012345q = 3q = 4q = 5a shaded dot is a collapsed thresholdm = 32 · n = 4,000 · 6 plantedno row collapsed
Fig. 5 The same sweep with a longer pattern. Every threshold is higher, every line starts lower, and the collapse moves right — the filter’s whole design space in one parameter.

The prediction, and what it is a prediction of

A window holds w=m+kq+1w = m + k - q + 1 qq-grams, and a random one is in the pattern’s profile with probability at most d/σqd/\sigma^q where dd is the number of distinct qq-grams the pattern holds. Treating them as independent gives a binomial, and the share of windows proposed is its upper tail at tt.

Compared against the measurement on a text with occurrences planted in it, that prediction is wrong by twenty orders of magnitude — and it is not the prediction’s fault. At q=4q = 4, k=0k = 0 the filter proposes exactly six windows and six occurrences were planted: the candidates are entirely the planted ones, and a prediction of chance candidates has nothing to be compared against.

This is the same correction the filter that feeds the table had to make in the other direction, where the term everybody drops is the planted one. A filter’s candidate count is a sum of two terms with different causes, and comparing a prediction of one against a measurement of both is how a working filter comes to look broken or a broken one correct.

A prediction of chance candidates, on a text with nothing plantedThe predicted share of windows proposed divided by the measured share, on 4,000 characters holding no occurrence of the pattern at all. Only the rows where the filter is still a filter and chance produces something are drawn — everywhere else the measurement is zero and the ratio is not defined. The prediction treats a window's q-grams as independent, which they are not, and the error runs in both directions: 2 of 3 rows over and the rest under. A tolerance fitted to either end would have been wrong at the other.q = 3, k = 34.9x0.33% measuredq = 3, k = 43.6x6.0% measuredq = 4, k = 40.8x5.2% measuredexactm = 24 · nothing plantedpredicted / measured share4,000 characters
Fig. 6 The prediction against a measurement on text with nothing planted in it, which is the only comparison the two are about the same quantity in.

On an empty text the prediction is right within a factor of five

Run the same sweep with no occurrences planted and the two quantities become comparable. Where the filter is still a filter and chance produces anything at all, the ratios are: at q=3,k=3q = 3, k = 3 the prediction is 4.89 times the measurement; at q=3,k=4q = 3, k = 4 it is 3.64 times; at q=4,k=4q = 4, k = 4 it is 0.80 times.

Over by a factor of five at the short qq and under by a fifth at the longer one. The direction is explicable — overlapping qq-grams are strongly dependent, which clusters the shared counts and makes the tail heavier than a binomial in some regimes and lighter in others — and the important thing is that the error runs both ways.

A tolerance fitted at q=3q = 3 would have been wrong at q=4q = 4 and the other way round, which is why the check requires the prediction to be within an order of magnitude and requires both an over-estimate and an under-estimate in the sweep. A one-sided check here would have been a fitted constant wearing a gate’s clothes.

A prediction of chance candidates, on a text with nothing plantedThe predicted share of windows proposed divided by the measured share, on 4,000 characters holding no occurrence of the pattern at all. Only the rows where the filter is still a filter and chance produces something are drawn — everywhere else the measurement is zero and the ratio is not defined. The prediction treats a window's q-grams as independent, which they are not, and the error runs in both directions: 2 of 3 rows over and the rest under. A tolerance fitted to either end would have been wrong at the other.q = 3, k = 34.9x0.33% measuredq = 3, k = 43.6x6.0% measuredq = 4, k = 40.8x5.2% measuredexactm = 24 · nothing plantedpredicted / measured share4,000 characters
Fig. 7 The same comparison over a wider grid, with the rows where chance produces nothing left out because a ratio against zero is not a number.

Why a larger qq proposes more: the candidates are clusters

“The threshold has fallen faster than the qq-grams have got rarer” is offered above as the reason q=5q = 5 proposes 121 windows where q=3q = 3 proposes 92. The arithmetic says something else, and it says it exactly.

Six occurrences were planted. A window that starts δ\delta positions away from a true occurrence still contains nearly all of it: it loses about δ\delta of the pattern’s qq-grams off one end and gains δ\delta unmatched ones at the other, so it shares roughly (m+1q)δ(m + 1 - q) - \delta. That window passes while

(m+1q)δ    m+1q(k+1),(m+1-q) - \delta \;\ge\; m + 1 - q(k+1),

which is δqk\delta \le qk. Each true occurrence therefore proposes a cluster of about 2qk+12qk + 1 windows, and the cluster’s width is proportional to qq.

Put the numbers in at k=2k = 2 with six occurrences: q=3q = 3 gives clusters thirteen wide and 78 candidates; q=4q = 4 gives seventeen and 102; q=5q = 5 gives twenty-one and 126. Measured: 92, 104 and 121. The whole candidate count is the planted occurrences smeared, and the smear grows linearly in qq.

The check at the other end confirms it. At k=0k = 0 the cluster width is one, so the prediction is six candidates — and q=4q = 4 and q=5q = 5 propose exactly six. The q=3q = 3 row proposes eight, the two extra being the chance term finally becoming visible at a qq short enough for a four-symbol alphabet to repeat.

So the rarity argument is not what decides this sweep; it is almost entirely absent from it. On a four-symbol alphabet with m=24m = 24, a random window shares about 8.3 four-grams by chance against a threshold of 13, and about 0.4 five-grams against a threshold of 10 — nowhere near passing in either case. The chance term is negligible at every qq measured, and the candidate count is a property of the tolerance band the threshold draws around each real occurrence.

That reframes the choice of qq in a way the sweep alone does not. Two regimes exist and they want opposite things. Where chance dominates — a short qq, a small alphabet, a long window — raising qq helps, sharply, because the chance term falls exponentially. Where the planted term dominates, raising qq hurts, linearly, because it widens every cluster. The optimum is the qq at which the two terms are comparable, which on this text is just below three.

And it says what a candidate count is actually measuring. A filter proposing 104 windows for six occurrences has a selectivity of 17 windows per answer, and 17 is 2qk+12qk + 1 rather than anything about the text — so quoting the ratio as a filter’s quality is quoting a number set by the threshold’s slack. The threshold that reaches zero is the same parameter at its other end, where the slack has swallowed the whole text; here it is swallowing a band around every answer, and the filter that proposes everything is what the far end of that looks like.

What the filter reads, and why it cannot do otherwise

Every plate in this strand reports the characters read as well as the cells computed, and for the counting filter the first number is always nn plus a few.

That is not a defect and it is not an accident of this implementation. The filter’s decision is a function of the window’s contents; a window that has not been read has no contents; and the windows overlap, so an incremental count reads each character once and then reuses it. There is no version of this algorithm that skips.

The comparison with the seed filter is therefore not “which is cheaper” but “which resource is scarce”. On the sweep at k=2k = 2: the counting filter reads 18,692 characters and computes 16,744 cells; the seed filter reads 780 characters, performs 879 index ranks, and computes 4,830 cells.

Different columns, no ordering, and the machine decides. Three savings in three currencies is where that is put on one page with a third method beside them.

What the two filters have in common

Both filters in this collection now rest on a pigeonhole argument, and the arguments are close enough that the difference between them is instructive.

The filter that feeds the table partitions the pattern into k+1k+1 disjoint pieces and observes that kk errors cannot touch all of them. The counting filter takes mq+1m-q+1 overlapping pieces and observes that kk errors cannot touch more than kqkq of them.

Disjointness gives a stronger conclusion — one piece survives intact, so it can be looked up exactly — at the cost of a small stock of pieces. Overlap gives a weaker conclusion — many pieces survive but which ones is unknown — from a large stock, so the conclusion has to be a count rather than an exact query.

That difference is what puts one filter behind an index and the other in front of a scan, and it is the mechanism behind their different currencies. An exact query needs a structure; a count needs a pass.

The check that must reject

The filter’s threshold is the one parameter that can be wrong without any symptom, so it gets the rejection test.

Raise tt by one and rerun. The windows kept are a subset of the correct ones, every occurrence found inside them is real, and the answer set is missing an occurrence — measured, one of twelve on a 2,500-character text at k=2k = 2.

Everything about that run looks healthy. The candidates are fewer, the cells are fewer, the saving is larger, and every reported occurrence verifies. The only way to see it is to compare against the whole table, which is what the gate does.

The complementary check is that lowering tt is safe but useless: at t0t \le 0 every window is proposed, the answers are all correct, and the filter has cost more than not filtering. Both directions are failures and only one of them is visible in the answers.

What the filter saves, until it does notA pattern of 24 characters in 4,000 characters of four symbols, with 6 occurrences planted at the stated error count. Each line is one q. The value is the whole table's cells divided by the filter's, so one is break-even. Every line reaches it inside this sweep, and the q that reaches it last is not the largest: a longer q-gram raises the threshold and lowers the chance of sharing one, and the two effects trade. The vertical axis is logarithmic.110100errors allowed, ktable cells / filter cells01234q = 4a shaded dot is a collapsed thresholdm = 24 · n = 4,000 · 6 plantedno row collapsed
Fig. 8 The saving at one q, which is the quantity the raised threshold improves while breaking the answer. A gate reading only this line would prefer the broken filter.

Where the lemma is tight

The rejection above says something about the lemma as well as about the code: raising the threshold by one loses an occurrence, so the bound is reached on this text rather than merely holding.

That is expected and it is worth confirming. The bound is tight when the kk errors are spread far enough apart to destroy qq distinct qq-grams each, which on a twenty-four-character pattern with two errors needs them about eight characters apart — a common arrangement rather than a contrived one.

When the errors are adjacent they destroy fewer, the occurrence shares more qq-grams than the bound requires, and a filter with a raised threshold would find it anyway. So the tightness is a property of where the errors fall, and a filter that was tuned on adversarially clustered errors would be unsafe on ordinary ones.

How many q-grams an occurrence must still share, m = 32Each cell is t = m + 1 − q(k+1): the number of q-grams a window must share with the pattern before it is worth verifying. The shaded cells are where t has reached zero or below, which is a demand of nothing — the filter proposes every window and has stopped being a filter. Nothing here is measured: the collapse is arithmetic, it is known before a text is read, and it happens at q(k+1) = m + 1.k = 0k = 1k = 2k = 3k = 4k = 5k = 6q = 231292725232119q = 330272421181512q = 4292521171395q = 52823181383-2q = 627211593-3-9q = 8251791-7-15-23a shaded cell is a threshold of zero or less: every window proposedt = m + 1 − q(k+1) · m = 326 collapsed cells
Fig. 9 The design space for a longer pattern, where the same errors destroy a smaller share of a larger stock of q-grams and the live region is correspondingly wider.

The honest limit

The texts here are drawn from a four-symbol alphabet with occurrences planted by substituting kk characters at random positions in a copy of the pattern. Real approximate matching is against corpora whose near-occurrences arrive from a process — sequencing error, typing, versioning — and the arrangement of errors within an occurrence is exactly what the tightness above depends on.

The sweeps run to k=4k = 4 or k=5k = 5 because past the collapse there is nothing to measure, and to four thousand characters because the baseline is a full table and the full table is quadratic. Nothing here establishes how the filter behaves at the lengths a real application uses; what it establishes is the shape, and the shape has an nn in neither the threshold nor the candidate fraction.

The one number that would change with scale is the crossing between filtering and not filtering, which depends on the ratio between a verification and a table row and therefore on nn. It is measured at one length here and is a line on a plate rather than a formula.

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 sizeApproximate matchingCounting filterEdit distanceFalse-positiveFilteringMeasurementPattern matchingPigeonholeQ gramSeed-and-extendSelectivityUnit of costVerification