When the algorithm is a table

A trie fitted to its queries

A nearest-word search over a trie of 2,424 words gives a column to about 30% of the prefixes each query, and it was proposed that a stream of queries would pick out a part of the trie worth keeping and a part worth collapsing. A hundred queries have visited 93% of it; two thousand, 99.5%. A stream does not choose a part of the trie. It chooses a distance: misspelt vocabulary words all lie within two edits, and a search that starts at the median past answer does a fifth of the work of one that discovers its bound. Misspellings of words the vocabulary lacks break that, unless the search tries the learned bound once and then gives it up.

The columns the candidates share laid a vocabulary out as a trie and computed one column of the edit-distance table per prefix rather than per word. A bound found during the search then did more than shorten a word’s table: a column whose smallest value passes the bound proves that nothing beneath it can come close, and takes the whole subtree with it. Over the 2,424 distinct words of the collection’s fixed corpus of essays, the walk gave a column to 2,299 of the 7,709 prefixes a query — about 30% — and read 16,958 cells where a table per word read 156,714.

Its closing section noticed that everything about that structure was a property of the vocabulary and nothing a property of the queries. A search that knew which prefixes its queries actually reach could keep those and collapse the ones never reached, fitting the trie to a workload rather than to a dictionary. It asked how much of the visited 30% is the same 30% from query to query, whether collapsing the rest is worth the space, and what happens when the queries are not misspellings of vocabulary words at all — since a spelling checker’s hardest inputs are words its dictionary does not have.

This page answers all three, and the first answer removes the premise of the proposal. A stream does not choose a part of the trie. It chooses something else, and that something is worth much more.

A stream visits the whole trie

The stream is 2,000 queries, each a word drawn from the vocabulary with one or two random typing errors: a letter inserted, deleted or replaced. A second stream of 600 queries is made the same way from words the vocabulary does not contain — 604 words from a second fixed corpus, of technical writing about programs, that the essays never use, such as instrumented, pretending and duration. Each query is walked through the trie with the bound discovered as the search goes, exactly as the earlier page walked it, and every node the walk gives a column to is recorded. Every answer is checked against the earlier page’s search.

One query visits 32% of the trie's prefixes; a hundred have visited 93% of them between them, and two thousand 99.5% — there is no part of the trie a stream leaves aloneThe distinct trie nodes given a column by the bounded walk, as a share of the 7,709 prefixes, against the number of queries seen. Misspelt vocabulary words: 1 query 2,472, 3 queries 3,109, 10 queries 3,930, 30 queries 5,346, 100 queries 7,205, 300 queries 7,365, 1,000 queries 7,621, 2,000 queries 7,673. Misspelt words it lacks: 1 query 1,026, 3 queries 3,295, 10 queries 5,378, 30 queries 6,584, 100 queries 7,699, 300 queries 7,704, 600 queries 7,705. Both axes are logarithmic.1101001,000queries seenshare of the trie's prefixes ever visited10%20%30%50%70%100%misspelt vocabulary wordsmisspelt words it lacks7,709 prefixes, 2,424 wordsthe bounded walk, bound discovered
Fig. 1 The distinct prefixes given a column, as a share of the trie’s 7,709, against queries seen; both axes logarithmic. Misspelt vocabulary words: 2,472 after one query, 3,930 after ten, 7,205 after a hundred — 93% — and 7,673 after two thousand. Misspelt words the vocabulary lacks: 1,026 after one, 5,378 after ten, 7,699 after a hundred.

A hundred queries have given a column to 93% of the trie between them, and two thousand to 99.5%. Any single query visits about a third, and consecutive queries share about 40% of what they visit. But the thirds are different thirds, and they cover the structure within a few dozen queries. A trie collapsed to the nodes a thousand queries never visited would collapse 88 nodes of 7,709. That is 1% of the structure, gone for nothing, since a later query can still need any of them.

The reason is in how a bounded walk spends its columns. Near the root every query visits nearly everything: the first letter or two of a query rarely rules out a branch, because a single edit can change the first letter. Deeper down, the walk follows the branches whose prefixes are close to the query’s own, and those depend on the query. A vocabulary of English words is dense at depth three or four, so across a few dozen queries with different spellings, the deep branches every query follows add up to nearly all of them.

The words the vocabulary lacks cover it even faster: a hundred of them visit 99.9% of the trie. A query with no near neighbour has a loose bound for longer and gives columns to more of the structure before it finds one.

Where the visits concentrate

Visited at least once is not the same as visited often, and the second is what a structure could exploit.

Ranked by how often the first 1,000 queries visited them, the top quarter of the prefixes — 1,927 nodes — holds 72% of a later query's visits, and 58% of a query for a word the vocabulary lacksPrefixes ranked by how many of the first 1,000 misspelt vocabulary words visited them; for each top share of the ranking, the mean share of a held-out query's visited nodes that fall inside it. Misspelt vocabulary words: top 5% (385 nodes) 24%, top 10% (771 nodes) 41%, top 25% (1,927 nodes) 72%, top 50% (3,855 nodes) 93%, top 75% (5,782 nodes) 99%, top 100% (7,709 nodes) 100%. Misspelt words it lacks: top 5% (385 nodes) 16%, top 10% (771 nodes) 29%, top 25% (1,927 nodes) 58%, top 50% (3,855 nodes) 83%, top 75% (5,782 nodes) 96%, top 100% (7,709 nodes) 100%. The diagonal is a ranking that knew nothing.00.2500.5000.750100.2000.4000.6000.8001share of the trie's prefixes, most visited firstshare of a later query's visits they holdmisspelt vocabulary wordsmisspelt words it lacksranked on 1,000 queries, measured on the restdashed: no ranking
Fig. 2 Prefixes ranked by how many of the first 1,000 queries visited them, and for each top share of the ranking, the share of a later query’s visits that land inside it. Misspelt vocabulary words: the top 5% hold 24%, the top 10% hold 41%, the top quarter holds 72%, and the top half 93%. Misspelt words the vocabulary lacks: 16%, 29%, 58% and 83%. The dashed diagonal is a ranking that knew nothing.

The visits are concentrated, and the concentration generalises from one set of queries to the next. The quarter of the prefixes that the first thousand queries visited most holds 72% of a later query’s visits. That is roughly the 80–20 shape of most workloads, and it survives on queries the ranking never saw. It survives less well on misspellings of words the vocabulary lacks, 58%, because those queries go down branches the vocabulary’s own misspellings rarely do.

What a hot core can buy is not pruning, since the cold three quarters are still visited. It can buy placement: storing the most-visited quarter of the nodes together, so a walk’s columns are computed from nodes that share cache lines. That is the question the count is not the time keeps separating from the cell count, and it is not measured here. What is measured is that a stream does not identify anything that can be thrown away.

What the stream does know

The structure is the wrong thing to fit, then. But the proposal’s instinct — that a stream of queries tells a search something its vocabulary does not — is right about something else.

What a stream knows that its trie does not: misspelt vocabulary words lie within two edits of their answer, 59% within one; misspellings of words it lacks average 3.44 and reach 22The share of queries whose nearest vocabulary word lies at each edit distance. Misspelt vocabulary words (mean 1.38): 0, 3%; 1, 57%; 2, 41%. Misspelt words the vocabulary lacks (mean 3.44): 0, 0.8%; 1, 9.8%; 2, 33.3%; 3, 24.5%; 4, 14.3%; 5, 7.0%; 6, 3.5%; 7, 1.5%; 8, 0.7%; 9, 0.8%; 10, 0.3%; 12, 0.3%; 13, 0.7%; 14, 0.3%; 15, 0.5%; 16, 0.2%; 17, 0.2%; 18, 0.3%; 20, 0.5%; 21, 0.2%; 22, 0.2%. The last column gathers ten and more.0%20%40%60%012345678910+edit distance to the nearest vocabulary wordshare of queriesmisspelt vocabulary wordsmisspelt words it lacks2,000 and 600 queries, 1 or 2 typing errorsnearest word in 2,424
Fig. 3 The share of queries whose nearest vocabulary word lies at each edit distance. Misspelt vocabulary words: 3% at distance 0, 57% at 1, 41% at 2 — nothing further, since each has at most two errors. Misspelt words the vocabulary lacks: 1% at 0, 10% at 1, 33% at 2, 25% at 3, 14% at 4, and a tail that reaches 22.

The stream knows how far its answers lie. Every misspelt vocabulary word has an answer within two edits, because a word with at most two typing errors is at most two edits from the word it came from. The typo sometimes lands on another word instead, which is why a few are at distance zero. That fact is not visible in the vocabulary and not in the trie; it is a property of the queries, and a stream reveals it after a handful of them.

The bound the search finds for itself put a price on not knowing the answer’s distance: a search told the true distance in advance reads far fewer cells than one that must discover it, because a tight bound prunes from the first column. On the trie the earlier page measured that price at 4.66. A stream offers a way to pay much less of it without being told anything: guess the bound from the answers the stream has already produced, start there, and widen only if nothing is found.

Starting from a bound, and paying when it is wrong

Starting at a bound of one and widening by one, a misspelt vocabulary word costs 3,726 cells against 17,621 for discovering the bound; a misspelt word the vocabulary lacks costs 102,691 against 35,306Mean cells a query for a search that starts at a fixed bound and widens it by one until a word is found, every attempt charged, against the starting bound. Misspelt vocabulary words: from 1, 3,726 in 1.41 attempts; from 2, 4,865 in 1.00 attempts; from 3, 9,528 in 1.00 attempts; from 4, 13,755 in 1.00 attempts; from 5, 16,178 in 1.00 attempts; discovering the bound 17,621; starting at the true distance 3,268. Misspelt words it lacks: from 1, 102,691 in 3.44 attempts; from 2, 101,923 in 2.55 attempts; from 3, 99,583 in 1.99 attempts; from 4, 95,701 in 1.68 attempts; from 5, 90,861 in 1.50 attempts; discovering the bound 35,306; starting at the true distance 27,702. The vertical axis is logarithmic; dashed lines are discovering the bound, dotted are knowing it.10⁴10⁵the bound the search starts atcells a query12345misspelt vocabulary wordsmisspelt words it lacksevery attempt chargeddashed: discovered · dotted: known
Fig. 4 Cells a query for a search that starts at a fixed bound and widens it by one until a word is found, every attempt charged; logarithmic vertical axis. Misspelt vocabulary words: from 1, 3,726 cells in 1.41 attempts; from 2, 4,865; from 5, 16,178. Discovering the bound (dashed): 17,621; starting at the true distance (dotted): 3,268. Misspelt words the vocabulary lacks: from 1, 102,691 in 3.44 attempts; from 5, 90,861; discovering 35,306; knowing 27,702.

For misspelt vocabulary words, starting low is worth nearly everything the oracle is worth. From a bound of one — widening to two when nothing is found, which 41% of queries need — the search reads 3,726 cells a query, against 17,621 for discovering the bound and 3,268 for being told the answer. A guess of one edit, widened once when wrong, recovers 96% of the gap to an oracle. Starting at two needs no widening at all and reads 4,865. Each bound above that reads more, because a looser bound prunes less, until at five the search is nearly as expensive as discovering.

For misspellings of words the vocabulary lacks, the same scheme is a disaster. Starting at one costs 102,691 cells a query, nearly three times discovering the bound, and starting at five still costs 90,861. Their answers spread from 0 to 22, and a search that widens one step at a time pays a full bounded walk at every step it takes. A query whose answer is at distance 7 walks the trie at bounds 1, 2, 3, 4, 5, 6 and 7, and each of those walks is a substantial fraction of the discovering walk, which found some word at distance 7 or so early and pruned from there.

So the scheme is right exactly when its guess is right, and the stream that makes the guess is only reliable for the kind of query it has seen. That is the same shape as the placement what the queries know that the map does not fitted to a destination and then watched decay when the traffic moved: what a workload teaches is worth a great deal on that workload and can cost more than nothing when the workload changes.

A bound learned from the stream, on a stream that changes

A real spelling checker sees both kinds of query mixed, and it cannot know in advance which one it has. The measurement that settles what to do mixes the two streams in stated shares and learns the starting bound as it goes: the median distance of the last 200 answers. Two policies use it. One widens by one until it finds a word. The other tries the learned bound once and, if nothing is within it, gives up on the guess and runs the discovering walk.

On a stream where a fifth of the queries are for words the vocabulary lacks, widening from the median past answer costs 23,555 cells a query, more than discovering the bound's 21,090; one try at the median and then discovering costs 13,028Mean cells a query on streams mixing misspelt vocabulary words with misspelt words the vocabulary lacks, in stated shares. The learned start is the median distance of the last 200 answers. Discover the bound: 0% 17,621, 5% 18,409, 10% 19,257, 15% 20,097, 20% 21,090. Start at the median, widen by one: 0% 3,812, 5% 8,209, 10% 12,446, 15% 16,990, 20% 23,555. Start at the median once, else discover: 0% 10,061, 5% 11,043, 10% 11,736, 15% 12,194, 20% 13,028.0102000.0500.1000.1500.200share of queries for words the vocabulary lacksthousands of cells a querydiscover the boundstart at the median, widen byonestart at the median once, elsediscovermedian of the last 200 answersevery attempt charged
Fig. 5 Cells a query against the share of queries for words the vocabulary lacks, in thousands. Discovering the bound: 17,621 with none, 21,090 with a fifth. Starting at the median and widening by one: 3,812 with none, 12,446 at a tenth, 23,555 at a fifth — above discovering. Starting at the median once and otherwise discovering: 10,061 with none, 13,028 at a fifth.

Widening from the learned bound is the best policy on a pure stream and the worst once a fifth of the queries are unfamiliar. It reads 3,812 cells a query when every query is a misspelt vocabulary word, less than a quarter of discovering. Each unfamiliar query costs it about three discovering walks, so its average climbs past discovering’s between 15% and 20% unfamiliar queries: 16,990 against 20,097 at 15%, 23,555 against 21,090 at 20%.

The single try never costs more than discovering, at any mixture measured. It reads 10,061 cells on the pure stream — worse than widening, because the median answer there is one edit and 41% of queries need two, and each of those pays the one-edit walk and then a full discovering walk. It reads 13,028 at a fifth unfamiliar, 38% under discovering’s 21,090. Its downside is capped at one bounded walk per query, and a bounded walk at a small bound is cheap.

The two policies are the two ends of a familiar trade. Widening is the bet that the stream’s past is a guide to its next query, taken at full stakes; the single try is the same bet with a floor under it. On a workload that is known to be homogeneous the first wins by a factor of 2.6. On one that is not, the second is the only one that never loses. A refit that changes one landmark reached the same kind of answer about when a learned placement should be trusted, from the counting of cells in a different search.

The learned quantile is a second dial, and it moves the pure stream’s cost more than the mixture’s. Set at the 80th percentile rather than the median, the learned start on misspelt vocabulary words is two, which contains every answer; the single try then never falls back and reads 4,938 cells, close to widening’s 3,812. Misspelt words the vocabulary lacks cost it 39,063 against discovering’s 35,306 — 11% more, the price of one wasted walk at bound two.

A stream that changes its mind

A mixture that is the same all the way through flatters any learned quantity, because the median of the last two hundred answers is always about the same. A stream that changes kind is the harder test: a thousand misspelt vocabulary words, then the 600 words the vocabulary lacks, then a thousand more familiar ones.

The learned median notices both changes quickly. Fifteen queries after the switch to unfamiliar words it has moved from one to at least two, and it comes back to one 163 queries after the switch back — the time it takes the last two hundred answers to be mostly familiar again. With a window of twenty answers rather than two hundred, it notices the first switch in eleven queries and the second in fifteen.

Noticing is not the same as being right. Over the whole switching stream, widening from the learned bound reads 26,162 cells a query with the short window and 26,228 with the long one, against 21,702 for discovering. The median of the unfamiliar answers is three, but their tail runs past twenty, and widening one step at a time from three still pays for every step to the tail. The single try reads 15,438 and 16,079. Its guess is wrong as often as widening’s, and every wrong guess costs one cheap walk and never a succession of them.

So the window is a minor dial and the policy is the major one. A learned bound follows a stream within a few dozen queries either way. What decides the cost is what a search does in the queries where the guess is wrong, and a policy whose wrong guesses are cheap does not need its guesses to be good. It is the trade what insurance against an estimate costs priced for a query planner: a planner that insures itself against a wrong row estimate gives up a little on the queries where its estimate was right, to cap what it loses on the ones where it was not. Here the premium is the one-edit walk a familiar query wastes when its answer is two edits away — the reason the single try reads 10,061 cells on a pure stream where widening reads 3,812 — and the cover is the succession of walks an unfamiliar query never has to make. Whether the premium is worth paying depends on how often the insured event happens, and at a tenth of the stream it already is.

What was measured and what was not

One vocabulary and one kind of misspelling. The queries have one or two random errors, drawn uniformly from insertion, deletion and substitution. Real typing errors are not uniform: transposed letters and errors near the keyboard’s neighbours dominate, and a real checker’s answer distances would pile up even more tightly at one. That would make the learned bound more valuable, not less.

The words the vocabulary lacks are a particular sample. They come from a second fixed corpus of technical writing, so they are technical English words, often longer than the essays’ vocabulary. A stream of proper names, foreign words or random strings would spread its answers differently, and every number on the mixed plate depends on that spread.

Cells, not time. Every count is a cell of an edit-distance table, which is the earlier pages’ unit. The two policies do different amounts of bookkeeping per query, and the placement a hot core would allow is a question about cache lines rather than cells. A band as wide as the answer is the other way this table is commonly cut, and its savings are counted in the same unit.

The collapse was not built. The visited-node counts say what a collapsed trie would have saved — 88 nodes after a thousand queries — and that was judged not worth building. A collapse fitted to a much larger vocabulary, where the deep branches are sparse, could find more to remove, and the count here is specific to a vocabulary of a few thousand words.

Still open: the bound as a function of the query

The learned bound here is one number for the whole stream: the median of recent answers. But the stream says more than that. A query’s own length and letters predict its answer’s distance. A long query with letters in an unusual order is more likely to be a word the vocabulary lacks, and a short query that is one letter off a common prefix is almost certainly a misspelt vocabulary word. The filter that feeds the table decided which candidates reach a table at all from a cheap test on each one, and the same kind of test could decide which bound a query starts from.

The measurement that follows predicts each query’s starting bound from features the search can compute before walking: its length, whether its first three letters are a prefix in the trie, and the distance at which the walk’s first column stops shrinking. It asks how much of the gap between the single try’s 13,028 cells and an oracle’s it closes on the mixed stream. The prediction is that the first-three-letters test alone separates most unfamiliar queries — a misspelling of a vocabulary word usually keeps a real prefix, and a word the vocabulary lacks often does not — so a two-bound policy, a start of one for queries with a known prefix and discovery for the rest, should come within a small factor of widening’s 3,812 on the pure stream and stay under discovering on every mixture.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

The objects this essay names

Each one links to every other essay that touches it.

CorpusDynamic programmingEdit distanceLower boundMeasured countOutput-sensitivePruningShared prefixSubproblemThresholdTrieWorkload