Learn · The Art of Not Looking
budding
The Index Inside the Index
A term-to-postings arrow hides a second search problem. Its representation must follow the questions, order, and memory level the dictionary actually serves.
“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.”
— Donald Knuth, “Structured Programming with go to Statements”, ACM Computing Surveys 6(4), 1974, p. 268
This is the second chapter in a book about search from first principles. The first chapter turned documents inside out and wrote a compact arrow from each term to its postings. Here you will open that arrow, derive four dictionary operations, prove the comparison lower bound for exact lookup, and compare six representations under three cost models. You will also learn two laws that survive every fast implementation: a hash says where to look, never what was found; and a result cap observes enumeration order. The next chapter asks what any successful lookup means when the document and query machines disagree about the word.
The arrow has work inside it
The inverted index from the previous chapter contained rows like these:
| Term | Postings |
|---|---|
| evidence | 5, 7 |
| field | 1, 2 |
| fox | 1, 2 |
| index | 3, 6 |
It is tempting to compress each row into an arrow:
fox → [1, 2]
But an arrow is notation, not an operation. Before reading [1, 2], the query engine must locate fox among all dictionary terms. A miss must stop at the right place. A prefix such as fi must find field without scanning unrelated terms. A capped completion must decide which five terms count as the first five.
The index therefore contains another index.
Lexicon — the finite vocabulary recognized by an index, together with the identities or payloads associated with its terms. In this chapter, “dictionary” and “lexicon” name that search structure, not a natural-language definition book. Learn more.
Before choosing a representation, name the questions. A useful static lexicon interface has four:
EXACT(x)— return the payload for , or report absence;LOWER-BOUND(x)— return the first term not less than ;PREFIX-SPAN(p)— return the half-open interval containing every term that begins with ;ENUMERATE(span, k, order)— return at most terms in the promised semantic order.
Exact lookup alone does not imply the other three. A hash table can answer the first without preserving any useful neighborhood. A sorted sequence answers the first three, but its lexical order may not be the authored or popularity order promised by a completion surface.
Start with the reference answer
A sequential scan is a poor default for a large lexicon and an excellent oracle. It states exact membership and source-order enumeration without hiding either behind a layout.
Reference exact lookup over a finite lexicon
SEQUENTIAL-EXACT(L, key)
Input: finite lexicon L[1:n] in source order, query key
Output: the payload paired with key, or NIL
for each entry e in L
if e.term = key
return e.payload
return NILThe loop performs at most equality tests and uses extra space. More importantly, every faster implementation can be checked against it:
For capped prefixes, the reference law is equally plain. Filter the source sequence by the prefix, then take the first . The cap comes last:
This order matters. Sorting matching terms first and then taking answers a different question whenever source order and lexical order differ.
A fast dictionary is correct only when it preserves both result identity and the semantic order observed by bounded enumeration.
Comparison search earns a logarithm
Sort distinct terms. An exact comparison search repeatedly asks whether the query lies before, at, or after a chosen term. For a successful lookup, there are possible answers. If absence must also identify one of the gaps, there are at least outcomes across hits and misses.
A binary decision tree of height has at most leaves. Distinguishing insertion gaps therefore requires
so
Binary search meets that envelope up to the exact convention used for hits and gaps. Its worst-case comparison count is logarithmic, its storage is the flat sorted sequence itself, and a prefix span is two boundary searches followed by the number of results actually returned.
Lower bound — a proof that no algorithm in a stated model can always use less than a given amount of work. This lower bound applies to comparison decisions; hashing and digital tries use stronger operations and therefore live in a different model. Learn more.
The model qualification is the lesson. A bytewise radix trie does not ask whether one whole string is less than another. It consumes key units and branches by their values. A hash table projects the key into a bucket. Neither contradicts the comparison lower bound because neither is confined to comparison decisions.
Six structures, four questions
The representations below are siblings, not a ladder from primitive to advanced.
| Representation | Exact lookup | Prefix span | Natural order | Main cost | ||||
|---|---|---|---|---|---|---|---|---|
| sequential sequence | comparisons | scan | source | touches everything | ||||
| sorted sequence | comparisons | lexical | compares shared prefixes repeatedly | |||||
| hash table | expected probes under its stated family and load | no direct span | bucket-dependent | residency, collision checks | ||||
| radix/PATRICIA trie | $O( | x | )$ key units | $O( | p | + r)$ | edge order | nodes and branches |
| front-coded blocks | boundary search plus bounded block decode | interval plus decode | lexical | restart interval and decoding | ||||
| minimal finite dictionary | $O( | x | )$ transitions | traversal from prefix state | transition order | static construction; shared futures |
Here is the length of the query key and is the number of enumerated results. The table deliberately does not crown a winner. It also leaves out constants that often decide the measurement: cache lines, pointer chasing, branch prediction, decoded bytes, and allocation.
Hashing: a locator followed by proof
A hash value is a many-to-one projection from a large key space into a smaller bucket space. Two distinct terms may therefore share a bucket. A correct exact lookup is:
Exact lookup through a collision-prone projection
HASH-EXACT(H, key)
Input: hash table H whose buckets retain original terms, query key
Output: the payload paired with key, or NIL
b ← H.BUCKET(HASH(key))
for each entry e in b
if e.term = key
return e.payload
return NILThe hash narrows the candidates. Equality proves identity. Removing the second step turns a collision from a performance event into a false result.
J. Lawrence Carter and Mark Wegman showed how choosing from a universal family gives expected guarantees independent of the input distribution cite:CARTER-WEGMAN. That is an expectation over the selected function, not permission to skip exact verification.
Tries: let the key choose the path
A trie shares the prefix sea among search, season, and seam. Each edge consumes a key unit. Exact lookup follows the complete key; prefix lookup stops at the prefix state and enumerates its descendants.
An ordinary trie may spend many nodes on paths with no branch. A compressed radix trie replaces each maximal one-child path with one labeled edge. The PATRICIA construction is a bitwise instance of this path compression. Its advantage is structural: the number of branch nodes follows distinctions among stored keys rather than every position in every key.
Front coding: keep restarts, compress between them
Lexically adjacent terms often share prefixes. A front-coded block stores one complete restart term, then records how much prefix each following term shares with its predecessor plus the remaining suffix. Larger blocks reduce repeated prefix bytes and increase the amount decoded after a seek. The restart interval is therefore a query-time decision disguised as a compression parameter.
Minimal dictionaries: merge equal futures
A trie shares prefixes. A minimal acyclic finite-state automaton also merges states whose possible suffixes are identical. The chapter on Dictionaries That Fit proves that quotient carefully. Here it is enough to name the placement choice: it is excellent for a static finite vocabulary and awkward for frequent mutation because one edit may change which futures remain equivalent.
One lexicon, several physical views
Suppose exact membership is frequent, prefix lookup is frequent, and capped results must remain in authored order. Hash order cannot serve prefix lookup. Lexical order cannot by itself recover authored order after a cap.
The honest representation is often one term store plus small views:
- a lexical permutation for boundary searches;
- a source-order ordinal for capped enumeration;
- optionally a hash projection for exact lookup;
- payload offsets shared by every view.
An ordinal permutation over entries requires at least enough bits to name one of positions per entry, or approximately
before compression and headers. That is not free, but it is usually cheaper and safer than duplicating the term bytes. It also exposes a useful invariant: every view resolves to the same canonical term identity.
Three cost models, not one winner
Asymptotic comparisons answer one question. A production choice needs at least three ledgers:
- abstract work — comparisons, probes, transitions, decoded key units;
- machine work — cache lines, branches, pointer dependencies, allocation;
- movement — bytes transferred from storage or network and bytes retained.
A flat sorted array can lose in comparison count and win in elapsed time while it fits in cache. A compact automaton can win in resident bytes and lose construction time. A hash table can win exact hits and make prefix enumeration impossible without another view. These are not contradictory benchmark results. They are different observations.
The workload needs strata too: successful exact hits, exact misses, narrow prefixes, absent prefixes, prefixes exceeding the cap, and deliberately forced hash collisions. Reporting only the average lets common easy hits conceal a broken or expensive miss path.
The lineage is wider than one tree
Edward Fredkin published “Trie Memory” in 1960 and named a digital structure whose path follows the key cite:FREDKIN. In 1968, Donald Morrison published PATRICIA, compressing digital paths for large information files cite:MORRISON. The lineage is not one inventor handing down one finished tree: digital search, path compression, hashing, front coding, and automaton minimization answer different parts of the lexicon contract. The useful historical question is the same as the engineering one: what operation and machine did each author actually have?
Where each structure is wrong
- A sequential sequence is right for a tiny, cold, rarely queried vocabulary.
- A sorted sequence is hard to beat when it is static and cache-resident.
- A hash-only dictionary is wrong when prefix or ordered range questions are part of the interface.
- A pointer-heavy trie is wrong when its node overhead dominates its strings.
- Front coding is wrong when frequent random hits repeatedly decode long blocks.
- A minimal automaton is wrong when updates are frequent or payload order is the primary contract.
The rule is not “benchmark everything forever.” First remove structures that cannot answer the required questions. Then compare the surviving structures in the workload and memory level that will actually use them.
Lessons
- The term-to-postings arrow contains a second search problem: locating and enumerating terms in the lexicon.
- Exact lookup, lower bound, prefix span, and capped ordered enumeration are distinct operations.
- Comparison lookup needs logarithmic height in the worst case; hashing and digital tries use stronger operations and belong to different models.
- A hash locates candidates. Exact equality establishes identity.
- A cap observes enumeration order. Reordering before capping changes results.
- One canonical term store can support several small physical views when one order cannot answer every question.
- Choose among representations only after naming abstract work, machine work, movement, and workload strata.
Practice
The dictionary can agree and still be wrong
The lexicon can now locate café exactly and enumerate every ca prefix in the promised order. But the document builder may have stored a composed é while the query arrives as e followed by a combining mark. Or one side may split camelCase while the other preserves it.
The next chapter makes that disagreement explicit. A lookup is meaningful only after indexing and querying share the same analyzer, ordering, and field provenance contract.
References
- Knuth. “Structured Programming with go to Statements.” ACM Computing Surveys, 1974. — primary source for the epigraph and its warning about optimizing before identifying the consequential work
- Fredkin. “Trie Memory.” Communications of the ACM, 1960. — early digital-search structure and the trie name
- Morrison. “PATRICIA—Practical Algorithm To Retrieve Information Coded in Alphanumeric.” Journal of the ACM, 1968. — compressed digital paths for information retrieval
- Carter and Wegman. “Universal Classes of Hash Functions.” Journal of Computer and System Sciences, 1979. — expected hashing guarantees independent of the input sequence
- Manning, Raghavan, Schütze. “Dictionary Data Structures.” Introduction to Information Retrieval, 2008. — search-engine dictionary representations and term lookup