Learn · The Art of Not Looking
budding
Two Lists, One Walk
Sorted postings turn conjunction into a walk whose cost follows the gaps in this instance, not merely the lengths of its lists.
“We develop the idea of a proof that a given set is indeed the correct answer.”
— Erik D. Demaine, Alejandro López-Ortiz, and J. Ian Munro, “Adaptive Set Intersections, Unions, and Differences”
This is the fourth chapter in a book about search from first principles. The previous chapter made indexing and querying agree on what a term means. Now a query contains two agreed-upon terms, each naming a sorted postings list. You will derive the linear merge and prove its invariant, replace repeated binary searches with stateful galloping, derive the comparison bound for a short list of length and a long list of length , and see why a shortest-list-first plan is a bound rather than a superstition. You will also derive the square-root skip heuristic and learn what an adaptive certificate says that worst-case notation cannot. The next chapter asks the question Boolean intersection cannot answer: did the matching words occur together?
Two guest lists at one door
A conference has an invited list and a checked-in list. Both are sorted by badge number:
invited 3 8 14 21 34 55
checked 1 8 13 21 22 34 89
The people who may enter are on both lists: . One method starts again at the top of checked for every invited badge. Another puts one finger on each list.
At 3 and 1, badge 1 is too small. Because the invited list is sorted, 1 cannot reappear later there. Advance only the checked-in finger. At 3 and 8, the same argument discards 3. Equality emits an answer and advances both.
One comparison has ruled out an entire future possibility. Order is doing the work.
Posting — a record saying that a term occurs in a document. A document-only postings list stores sorted document identifiers; richer postings may also store counts or positions. Learn more.
For two query terms and , Boolean AND asks for the set intersection
where is the set of documents containing term . OR is union and a AND NOT b is relative complement. The index has turned words into ordinary set algebra over integers.
The reference walk
Let and be strictly increasing sequences. The reference algorithm carries two forward-only indices.
Linear intersection of two sorted sets
MERGE-INTERSECT(A, B)
Input: strictly increasing sequences A[1:m] and B[1:n]
Output: the strictly increasing sequence A intersect B
i ← 1
j ← 1
R ← empty sequence
while i ≤ m and j ≤ n
if A[i] = B[j]
append A[i] to R
i ← i + 1
j ← j + 1
else if A[i] < B[j]
i ← i + 1
else
j ← j + 1
return RThe loop invariant is the whole proof:
Before each comparison, R is exactly the intersection of the consumed prefixes, and no consumed value can match an unconsumed value.
Initially both prefixes are empty. On equality, the shared value belongs in the answer and strict increase means it occurs nowhere else. If , then every unconsumed value in is at least , so can never match; advancing is safe. The other inequality is symmetric. When either list ends, no further pair can match. Thus R is exactly .
Every iteration advances at least one index and neither retreats. The time is comparisons and the working space is beyond the output. If the consumer accepts a stream, the result need not be retained at all.
The bound is tight for this algorithm, but not for every instance. Consider
A = [47]
B = [4, 6, 10, 12, 14, 16, 18, 20, 22, 32, 47, 81, ...]
A merge walks ten losing values before reaching 47. Sorted random access lets the short list ask a sharper question: where could 47 occur in the remaining suffix of ?
Gallop, then close the bracket
Binary search from the beginning of for every member of costs . It throws away information: after searching for , the next target is larger, so it cannot lie before the position just reached.
A finger search retains that position. A galloping search probes offsets until it passes the target, then binary-searches only the bracket just found.
Galloping search — exponential search from a known lower bound, followed by binary search inside the first power-of-two bracket containing the target. Its cost is logarithmic in the distance traveled, not in the whole sequence. Learn more.
Lower-bound search from a forward-only finger
GALLOP-LOWER-BOUND(B, start, target)
Input: strictly increasing B[1:n], valid start, target not before B[start]
Output: least j at or after start with B[j] ≥ target, or n + 1
if start > n or B[start] ≥ target
return start
step ← 1
while start + step ≤ n and B[start + step] < target
step ← 2 × step
low ← start + step / 2 + 1
high ← min(start + step, n)
while low ≤ high
middle ← low + floor((high - low) / 2)
if B[middle] < target
low ← middle + 1
else
high ← middle - 1
return lowGalloping intersection, short list against long list
GALLOP-INTERSECT(A, B)
Input: strictly increasing A[1:m] and B[1:n], with m ≤ n
Output: the strictly increasing sequence A intersect B
j ← 1
R ← empty sequence
for each value x in A
j ← GALLOP-LOWER-BOUND(B, j, x)
if j > n
return R
if B[j] = x
append x to R
j ← j + 1
return RThe finger never moves backward, so the intervals searched for successive targets are disjoint. Suppose their traversed lengths are . Galloping and closing a bracket of length takes comparisons. Because the finger crosses at most positions,
The logarithm is concave. Jensen's inequality therefore gives
Because , this is at most
Thus the intersection costs
When , this beats touching all entries and improves on unrelated searches. When and are similar, the bound becomes , but the plain merge often wins in elapsed time: it has a predictable sequential access pattern and less branch machinery.
Use a merge when both lists deserve a finger. Use galloping when one list can serve as a small sequence of questions into the other.
More than two terms
For a conjunctive query with postings , intersection is associative:
The answer does not depend on parenthesization. The work does. A simple small-versus-small plan sorts the lists by increasing document frequency and repeatedly intersects the current result with the next list.
Shortest-list-first conjunctive evaluation
SVS-INTERSECT(L)
Input: finite sequence L of sorted postings lists
Output: the intersection of every list in L
order L by nondecreasing length
R ← first list in L
for each remaining list P in L
R ← GALLOP-INTERSECT(shorter of R and P, longer of R and P)
if R is empty
return R
return RAfter any step, . Starting with the shortest list gives the smallest available upper bound on every later intermediate result. This does not prove that length order wins on every concrete set: contents, compression, cache residence, and correlations can reverse two plans. It proves the worst-case cardinality fact the planner is using.
The two main evaluation views now have names:
- term-at-a-time combines one whole term's evidence with an accumulator, then moves to the next term;
- document-at-a-time advances several postings cursors in document order and finishes one candidate document before the next.
Boolean pairwise intersection looks term-at-a-time because it materializes an intermediate set. The forward cursors inside it already foreshadow document-at-a-time ranked retrieval: later chapters will keep several cursors aligned and use score bounds to decide which document can be skipped.
A skip is a stored promise
Sequentially encoded postings may not support constant-time random access. A skip pointer stores a shortcut from one decoded position to a later one. It may be followed only when its destination is still below the opposing target; that comparison proves every posting under the jump cannot match.
How far apart should skips be? Let a list contain postings and place a skip every postings. A deliberately simple model charges about skip checks to cross the list and at most ordinary advances after the last useful jump:
By the arithmetic–geometric mean inequality,
with equality at . This derives the familiar square-root spacing heuristic: about skips, each spanning about postings.
It is a model, not a universal optimum. It ignores the query distribution, compressed-block boundaries, cache lines, update costs, and the possibility that checking a skip costs more than advancing one decoded integer. The Stanford information-retrieval text presents square-root spacing as a practical heuristic and explicitly notes those limitations.
The input carries its own difficulty
Worst-case analysis asks for one bound covering every pair of lengths . Adaptive analysis asks what this particular arrangement forced an algorithm to learn.
Certificate — a set of observed comparison outcomes sufficient to prove the reported answer. A shortest certificate is a lower-bound witness for that instance: any comparison-based algorithm must obtain enough information to distinguish the answer from a different legal input. Learn more.
Suppose the two lists occupy distant, cleanly separated ranges. A few boundary comparisons can certify that their intersection is empty. Suppose instead that their values alternate and nearly coincide. Many local comparisons may be unavoidable. The lengths are identical in both cases; the proof burden is not.
In 2000, Erik Demaine, Alejandro López-Ortiz, and J. Ian Munro formalized this view for intersection, union, and difference of sorted sets. They characterized proofs of an answer and gave comparison algorithms within a constant factor of an instance-sensitive difficulty measure. The theorem is stronger than “it ran quickly on sparse lists”: it names the information any correct comparison algorithm must expose on that input.
Text retrieval supplied the pressure. Boolean queries over an inverted file look like ordinary set operations, yet repositories produce posting lists with wildly different lengths and arrangements. A worst-case bound treats a clean million-element gap and a million ambiguous near-matches alike. Demaine, López-Ortiz, and Munro changed the question from “how large are the sets?” to “how short can a proof of this answer be?” The resulting adaptive framework made the instance—not an average distribution guessed in advance—the unit of difficulty.
The full instance-optimal result needs a careful certificate definition and comparison-model proof; it is cited here rather than disguised as the short galloping derivation above. Galloping is one concrete lesson from the same stance: retain what earlier comparisons proved, and let long gaps be cheap.
The machine underneath the notation
Asymptotics decide which growth curves are possible. Hardware decides which curve wins on a given machine.
J. Shane Culpepper and Alistair Moffat compared intersection methods for inverted indexes and separated two representation regimes: sorted arrays with constant-time indexed access, and sequential or compressed lists where reaching the $d$th integer has its own cost. That distinction changes which “skip” is physically available.
For flat resident arrays:
- merge streams contiguous memory and gives prefetchers an easy job;
- galloping performs fewer comparisons on imbalanced inputs but probes less predictably;
- branch mispredictions can cost more than several integer comparisons;
- vectorized block comparisons can move the crossover point.
For compressed postings:
- a logical jump may first require finding and decoding a block;
- auxiliary samples consume bytes but can bound the decode distance;
- the best skip spacing follows blocks and query frequencies, not merely ;
- dynamic updates can invalidate a layout whose performance assumed a static list.
Measure at least comparisons, decoded integers, bytes touched, and elapsed time. Reporting only the last number hides why the winner changed. Reporting only comparisons pretends the memory hierarchy does not exist.
What not to do
Several plausible approaches fail for different reasons.
| Approach | Why it is tempting | Exact failure | Reversal condition |
|---|---|---|---|
| scan every document | no index machinery | work follows corpus size, not evidence | tiny or one-shot corpora |
| restart a scan for each short-list value | simplest membership test | repeats already disproved prefixes | opposing list is itself tiny |
| independent binary searches | logarithmic lookup | discards the previous lower bound | parallel immutable probes or no ordered query sequence |
| gallop every pair | adaptive-looking default | extra probes and branches lose on balanced lists | lists are strongly imbalanced |
| add skips everywhere | visible shortcuts | metadata and checks dominate short lists | long sequential encodings with repeated conjunctions |
| always trust shortest-list-first | cheap cardinality heuristic | length omits correlation and physical cost | no better statistics are available |
The honest production rule is a dispatch, not a champion: compare list lengths and representation capabilities, choose a sequential merge for balanced resident lists, choose finger or galloping search for strong imbalance, and measure the threshold on the actual encoding and machine. Preserve a simple merge as the differential oracle.
The unresolved match
Intersection has now proved that every returned document contains every query term. It has not proved what the reader probably meant.
A document containing “new evidence arrived yesterday” in its first paragraph and “York hosted the meeting” in its last contains both new and york. It survives the Boolean intersection for “New York.” The lists have met; the words have not.
To answer that question, a posting must remember where a term occurred. The next chapter turns a phrase into another intersection—this time shifted by an offset—and makes us pay explicitly for storing position.
Lessons
- A comparison between sorted cursors proves which smaller value cannot match later; this is why linear merge is correct.
- Linear merge costs time and working space beyond its output.
- A forward-only galloping intersection costs for because it pays logarithmically for disjoint gaps.
- Shortest-list-first minimizes a worst-case bound on intermediate cardinality; it does not know correlations or physical access costs.
- The square-root skip rule follows from the model and remains a heuristic, not a law of storage.
- A certificate measures the comparison evidence this instance needs, which worst-case length bounds deliberately erase.
- Fewer comparisons need not mean less time; layout, decoding, branches, and cache traffic choose the practical crossover.
Practice
- Trace
MERGE-INTERSECTon[3, 8, 14, 21, 34, 55]and[1, 8, 13, 21, 22, 34, 89]. Record every comparison and restate the invariant after each emitted match. - For and , compare the upper bounds for merge, independent binary searches, and forward galloping. State what each bound omits about memory access.
- Derive the minimizer of for a skip check that costs ordinary advances. What happens to the spacing as skip checks become more expensive?
- Construct two pairs of lists with the same lengths: one whose empty intersection has a short comparison certificate, and one whose answer requires comparisons throughout the range.
- Design a dispatcher using a measured imbalance threshold. State the reference oracle and the observations a differential test must preserve.
References
- Erik D. Demaine, Alejandro López-Ortiz, and J. Ian Munro. “Adaptive Set Intersections, Unions, and Differences.” Proceedings of the Eleventh Annual ACM–SIAM Symposium on Discrete Algorithms, 2000.
- J. Shane Culpepper and Alistair Moffat. “Efficient Set Intersection for Inverted Indexing.” ACM Transactions on Information Systems 29, no. 1, 2010.
- F. K. Hwang and Shen Lin. “A Simple Algorithm for Merging Two Disjoint Linearly Ordered Sets.” SIAM Journal on Computing 1, no. 1, 1972.
- Christopher D. Manning, Prabhakar Raghavan, and Hinrich Schütze. “Faster Postings List Intersection via Skip Pointers.” In Introduction to Information Retrieval. Cambridge University Press, 2008.