CS
Algorithms
Problems Base course RU

SRT Searching and sorting · Algorithm

Interpolation search

Also known as: Interpolation search · interpolating search · learned index · shrinking cone · PGM-index · FITing-tree

Nobody opens a dictionary in the middle to look up “zebra”: they open it near the very end. Interpolation search does the same with numbers: it estimates the place from the values at the edges. On uniform keys log₂ log₂ n probes are enough, about four among 42 thousand hashes, where binary search needs fourteen. On city populations the same search spends hundreds and thousands of probes. Why, how to insure against it, and what grew out of this idea in databases.

Intermediate 18 minutes searching binary search measurement classic interviews

Complexity

Time
≈ log₂ log₂ n on average, n in the worst case
Memory
O(1)

on average on uniformly distributed keys; alternating with halving, at most 2⌊log₂ n⌋ + 2 probes; a probe costs more than in binary search: it has a multiplication and a division

Know first

Binary search

In the base course

20. The sorting tournament

Where it runs

  • the git pack index: a table of 256 counters by the first byte of the hash narrows the binary search
  • the oid_pos function in git: the first probe by interpolation, the rest by halving
  • learned database indexes (FITing-tree, PGM-index): the key curve in straight pieces with error ±ε
  • dictionaries with thumb-index letter tabs on the page edges

History

Wesley Peterson, 1957

Contents

  1. The idea
  2. Why so few probes
  3. Where it breaks
  4. Insurance
  5. A curve of straight pieces
  6. Code
  7. Probes and time
  8. What it costs
  9. Where it works
  10. History
  11. Problems

When people look up “zebra” in a dictionary, they do not open it in the middle: they open it near the very end, because “z” is the last letter. Binary search cannot do that: it halves the range without looking at what it is searching for. Interpolation search does look: it estimates the place from the values at the edges of the range and probes there. Below are five sorted rows from 45,227 cities of the world: the hashes of the names, the names themselves as in a phone book, latitudes, populations, and a made-up row with a “giant”. The scene’s window follows the range that is left, and the chord between its edges always runs along the window’s diagonal.

On the hashes the target is found in three or four probes, while binary search needs fourteen to sixteen on the same 42,311 numbers. A hash mixes the bits, the values lie evenly, and the curve in any window is almost a diagonal. On populations the search crawls: it finds a town of 22,026 people in 2139 probes. The population curve is a long shelf of tens of thousands of small towns and a cliff upwards where the cities of millions are. The chord runs high above the shelf and points almost at the left edge, and as you zoom in the picture repeats.

The idea

Interpolation search is built like binary search: a probe, a comparison, the range narrows. The difference is the choice of place. Binary search takes the middle, while interpolation search draws a straight line through the edges of the range, the points (lo, a[lo]) and (hi, a[hi]), and probes where it reaches the height of the target x.

The target stands above the lower edge by a fraction (x − a[lo]) / (a[hi] − a[lo]) of the whole rise. If the values grow evenly, it is the same fraction of the range away from its start:

fraction = (x − a[lo]) / (a[hi] − a[lo]),
pos = lo + ⌊fraction · (hi − lo)⌋.

After that everything is as in binary search. If the target is at pos, done; if what is there is smaller, search to the right of pos, if larger, to the left. The search goes on while the target lies between the values at the edges: with x < a[lo] or x > a[hi] it is not in the range. This condition is needed not only to stop in time.

If a[lo] ≤ x ≤ a[hi] and a[lo] < a[hi], then lo ≤ pos ≤ hi.

From the condition 0 ≤ x − a[lo] ≤ a[hi] − a[lo], so the fraction lies between 0 and 1, and ⌊fraction · (hi − lo)⌋ between 0 and hi − lo. Adding lo gives lo ≤ pos ≤ hi.

Without checking the edges the fraction goes outside [0, 1], and the probe flies off beyond the array. When a[lo] = a[hi], division is impossible, but then all the values of the range are equal and any place can be probed; the listing takes lo.

The product (x − a[lo]) · (hi − lo) for 64-bit keys does not fit in 64 bits: a key around 1018 times a million places is 1024. In Python integers grow by themselves. In Go the listing computes the product in 128 bits, bits.Mul64, and divides it with bits.Div64. The quotient is at most hi − lo, so it fits.

Why so few probes

Take a model in which the method is good. Let the n keys be independent random numbers spread uniformly over the interval [A, B], with the ends A and B known. We search for a number x; let K be how many keys are at most x, that is, the place where x belongs. The first probe guesses it as np, where p = (x − A)/(B − A).

In this model the first probe is off by at most √n / 2 places on average: E|K − np| ≤ √n / 2.

Each key is at most x with probability p, independently of the others, so K is a sum of n independent terms equal to 1 with probability p and 0 otherwise. Its mean is np, exactly the guess, and its variance E(K − np)² = np(1 − p) ≤ n/4: the product p(1 − p) is at most 1/4. The mean square of a quantity is at least the square of its mean absolute value, so E|K − np| ≤ √(E(K − np)²) ≤ √n / 2.

On a million keys the first probe misses by at most 500 places on average, while binary search after its first probe knows only which half the target is in. After that the same picture repeats on a smaller scale, and here it is an estimate rather than a proof. After a probe the new chord starts from a value close to x, and the fraction p in the new problem is small, of the order of the error divided by the range length m. The variance mp(1 − p) is then of the order of the previous error, and the new error is of the order of the square root of the previous one. The range where the target can still be shrinks as n, √n, n1/4, …, and after k probes it is of order n1/2k. It gets down to a few places when 2k ≈ log₂ n, that is, at k ≈ log₂ log₂ n. For a million keys that is 4.3 probes, for a billion 4.9.

This was proved rigorously in 1978 by Yehoshua Perl, Alon Itai and Haim Avni: about log₂ log₂ n probes are enough on average, and making noticeably more is very unlikely. On the hashes from the scene log₂ log₂ 42,311 ≈ 3.94, and the listing below measured 4.0 on average.

Where it breaks

Everything rests on the curve of values being close to a straight line. If it is not, the chord lies, and one can build a row where it lies every time.

Let n ≥ 2 and the array be 1, 2, …, n − 1 with one number G ≥ n² at the end. Then searching for the number n − 1 takes n − 1 probes.

Indices are from zero, so a[i] = i + 1 for i ≤ n − 2 and a[n − 1] = G; the target is at place n − 2. We show that with lo = i ≤ n − 2 and hi = n − 1 the probe lands exactly on lo. The numerator (x − a[lo]) · (hi − lo) = (n − 2 − i)(n − 1 − i) is at most (n − 2)(n − 1) = n² − 3n + 2. The denominator a[hi] − a[lo] = G − i − 1 is at least n² − n + 1, which is more than the numerator. So fraction · (hi − lo) < 1 and pos = lo. While i < n − 2, the value there is i + 1 < x: lo moves one place, and hi does not change. The probes go through places 0, 1, …, n − 2, n − 1 in all, and the last finds the target.

In the scene this is the “giant” row: 1 … 4999 and 1018. The window does not narrow: one place is nipped off on the left at a time, and the giant in the corner keeps pulling the chord down. Real data break the search more gently, but for the same reason.

  • Populations. The median city of 45,227 has 25,247 people, 73% of the cities have fewer than 50,000, and the largest has almost 25 million. The curve is a shelf with a cliff, and the cliff plays the giant’s role: 1587 probes on average, 2211 at worst.
  • The phone book. A name is a number made of its first four letters in base 27, as if the book were opened alphabetically. But the letters are unequal: 4898 names begin with S, 211 with X. Interpolation makes 17.6 probes on average, 60 at worst, and binary search 13.1.
  • Latitudes. Almost two thirds of the cities lie between 20° and 60° north, and there are almost none towards the poles. A chord drawn through the extreme latitudes misses: 16.0 probes against 14.0.

Worst of all, this cannot be seen in advance: on the hashes there are almost four times fewer probes than for binary search, on the names more, and on one and the same data different targets behave differently.

Insurance

The cure is simple: alternation. An odd probe goes by the chord, an even one into the middle of the range. The halving steps guarantee that the range shrinks, and the chords keep the luck where the curve is even.

Search by turns on any sorted array of n elements makes at most 2⌊log₂ n⌋ + 2 probes.

Let the range have m elements. A probe into the middle mid = ⌊(lo + hi)/2⌋ leaves either mid − lo = ⌊(m − 1)/2⌋ or hi − mid = ⌊m/2⌋ elements, at most ⌊m/2⌋. A probe by the chord does not enlarge the range: place pos drops out, and there are at most m − 1 elements left. A pair of probes, by the chord and then into the middle, leaves at most ⌊(m − 1)/2⌋ ≤ ⌊m/2⌋. After k pairs at most ⌊n/2k⌋ elements are left, and at k = ⌊log₂ n⌋ + 1 that is zero. So the search ends after at most 2(⌊log₂ n⌋ + 1) probes.

The worst case is twice as bad as binary search, but on average on our rows it comes out like this:

RowBy the chordBy turnsBinary
hashes4.06.714.4
the phone book17.612.913.1
latitudes16.010.214.0
populations1587.212.313.9
1 … 4999 and the giant2476.04.911.3

On latitudes the search by turns beats both. The halving steps cut off the uneven pieces, and on a short range any smooth curve is almost straight, so the chord hits again. Hence the next step: if the curve is straight piece by piece, it can be cut into pieces in advance.

A curve of straight pieces

Cut the row into pieces so that in each piece the line through its first key guesses the place of any key with an error of at most ε. A piece takes keys one at a time while such a line exists: each key allows its own range of slopes, and their intersection narrows like a cone with its apex at the first key. When the intersection is empty, a new piece begins. The index stores the starts of the pieces and the slopes. A search finds the piece by binary search over the starts, estimates the place by its line and looks for the target among the 2ε + 1 neighbouring places.

City populations with ε = 64 fit into 18 pieces. Bookmarks every 129 keys, which would give the same window, would need 238. Six pieces are enough for the hashes instead of 328, and 25 for latitudes instead of 252. There are about as many probes as for binary search; the gain is in memory: for populations the index is 13 times smaller, for hashes 55 times, and the last probes go within 129 neighbouring keys, which for 64-bit keys is one kilobyte of memory.

This is how the learned indexes of databases are built. FITing-tree in 2019 cut the key curve with a greedy “shrinking cone”, PGM-index in 2020 so that there were as few pieces as possible, and built the same kind of pieces over the starts of the pieces. This is interpolation search that has, instead of one chord for the whole curve, a chord for each almost straight piece.

Code

The listing builds from 45,227 cities the same five rows as the scene and searches each for every 50th value in three ways: by the chord, by turns and by binary search. For each way it prints the mean and the largest number of probes.

sorted row                 size  interpolation       by turns         binary
                                    mean   max     mean   max     mean   max
hashes of the names       42311      4.0     9      6.7    15     14.4    16
names, a phone book       17205     17.6    60     12.9    23     13.1    15
latitudes                 32407     16.0    43     10.2    17     14.0    15
populations               30576   1587.2  2211     12.3    23     13.9    15
1 .. 4999 and one giant    5000   2476.0  4951      4.9    15     11.3    13

The largest number of probes for the giant, 4951, is the worst-case claim in action: the target at place 4950 is searched for by probes at places 0, 1, …, 4950. Search by turns on the same targets makes at most 15 probes, and its bound is 2⌊log₂ 5000⌋ + 2 = 26.

Probes and time

A probe is not a unit of time. A probe of binary search has comparisons and a shift, while a probe by the chord also has subtractions, a multiplication and a division, in Go 128-bit ones at that. The benchmark below looks up random keys among n random 63-bit ones; on them the curve is even, as on hashes.

On a million keys search by the chord makes 4.4 probes on average, binary search 19.0: 4.3 times fewer. But in time the difference is almost twofold in Go and 1.4 times in Python. On a thousand keys in Python search by the chord does not win: the interpreter spends as much on multiplying and dividing big numbers as was saved on probes. The gain grows with n: when the array does not fit in the cache, what becomes expensive is each probe, not the arithmetic in it.

Others have seen something similar. Perl, Itai and Avni wrote in 1978 that in a machine’s memory interpolation search runs level with binary search and is slightly faster only on files of more than 5000 records; the gain in probes matters when each probe is a disk read. In git interpolation over hashes was tried from 2007: SHA-1 hashes are uniform, there is no better case. In 2017 it was removed: on git rev-list --objects --all in the Linux kernel repository it gave 37.4 seconds against 35.4 without it. And in 2019 Peter Van Sandt, Yannis Chronis and Jignesh Patel showed that a careful implementation that reuses the slope of the previous chord can be up to four times faster than binary search on uniform data.

What it costs

MethodProbes on averageProbes in the worst caseMemory
binary search≈ log₂ n⌊log₂ n⌋ + 1O(1)
by the chord≈ log₂ log₂ n on uniform keysnO(1)
by turnsdepends on the data2⌊log₂ n⌋ + 2O(1)
pieces with error ε—⌊log₂ m⌋ + ⌊log₂(2ε + 1)⌋ + 2O(m) for m pieces

The last row is two binary searches: over the m starts of the pieces and over a window of 2ε + 1 places. Cutting into pieces with a greedy cone is one pass, O(n). The average case by the chord is log₂ log₂ n only for uniform keys. On other distributions it can be anything, up to linear, and without insurance this search is sensibly used only where the shape of the data is known in advance.

Where it works

  • git. The pack index of objects has a table of 256 numbers: how many hashes begin with a byte no greater than the given one. By the first byte of a hash the table at once gives a range of length about n/256, and the binary search runs over it. This is interpolation done in advance and with a table. And the oid_pos function in git’s code makes only the first probe by the chord, by a pair of hash bytes, and halves after that.
  • Learned indexes. FITing-tree, PGM-index and their descendants store, instead of the pages of a B-tree, straight pieces with a guaranteed error. The article’s problem is about this.
  • Dictionaries. Letter tabs cut into the page edges are the same table as in git: the share of pages for each letter is known in advance, and interpolation is left to do inside the letter.
  • Roots of equations. The secant method (regula falsi) looks for the root of a monotonic function the same way: it draws a line through the ends of the interval and takes the point where it crosses zero. And the illness is the same: on a convex function one end gets stuck, and convergence becomes slow. So is the cure: Brent’s method, for example in scipy.optimize.brentq, interleaves chords and parabolas with bisection.

History

Search by estimating the place was described by Wesley Peterson in 1957, in a paper on finding records in random-access storage. Four years later Peterson would devise the cyclic redundancy check, CRC. Peterson believed that on average the search needed no fewer than half of log₂ n accesses. The estimate turned out too pessimistic. In 1976 Andrew Yao and Frances Yao proved by an intricate combinatorial argument that on random uniform tables log₂ log₂ n is enough, and that no search can do noticeably better on average. In 1977 Perl and Edward Reingold gave an intuitive explanation, and in 1978 Perl, Itai and Avni their own proof and an experiment: on 400,000 uniform keys 4.28 accesses on average with log₂ log₂ n = 4.21.

After that the method was considered a pretty theory for decades: in memory it lost to binary search because of the cost of a probe, and on non-uniform data because of the number of probes. It came back with learned indexes: if the curve is not straight as a whole, it can be made straight piece by piece.

Problems

In the problem the moments of earthquakes, the populations and the latitudes of cities have to be cut into straight pieces with that very shrinking cone, and the pieces counted. The keys go up to 2·1018, and the slopes have to be compared exactly.

An index of straight lines

Data: Places: GeoNames, CC BY 4.0 Earthquakes: USGS catalogue, public domain

See also

SRT Algorithm Binary search Finds the “no → yes” boundary in ordered data in log₂ n checks. O(log n) 2 problems SRT Algorithm Exponential search Steps 1, 2, 4, 8, … until you overshoot, then binary search: when the bound is unknown or the answer is near. 2⌊log₂ i⌋ + 1 questions to position i 1 problem DS Data structure B-tree A tree with hundreds of keys in a page-sized node: a search among a billion keys reads five pages. O(log n) comparisons, O(log_B n) page reads 2 problems NUM~ Algorithm Bisection and Newton's method A root of an equation: halving an interval is reliable, Newton's tangents are fast. bisection: log₂((b − a)/ε) steps; Newton: about log₂ of the number of digits 2 problems DS Data structure Hash table Search, insert and delete by key in O(1) on average: the key turns into the number of an array cell. O(1) on average 2 problems TEC Technique How to benchmark honestly Repetitions, warm-up, median and minimum, timeit and testing.Benchmark—and the usual ways to fool yourself with a measurement. enough repetitions for a series to take at least a tenth of a second 1 problem
← Previous Searching and sorting Exponential search Next → Searching and sorting External sorting

Written by Viacheslav Legostin. Everything is free; readers’ code is not stored.

Computer Science The algorithms catalogue Problems Base course Mathematics legost.in

The algorithms catalogue

Contents

  1. The idea
  2. Why so few probes
  3. Where it breaks
  4. Insurance
  5. A curve of straight pieces
  6. Code
  7. Probes and time
  8. What it costs
  9. Where it works
  10. History
  11. Problems

Problems

SRT Searching and sorting

  1. Binary search
  2. Binary search on the answer
  3. Exponential search
  4. Interpolation search
  5. External sorting
  6. Heapsort
  7. Counting sort and radix sort

DS Data structures

  1. Dynamic array
  2. Linked list
  3. Hash table
  4. Cuckoo hashing
  5. Bloom filter
  6. Binary heap
  7. Binary search tree
  8. AVL tree
  9. B-tree
  10. Disjoint set union
  11. Fenwick tree
  12. LRU cache

GRA Graphs

  1. Storing a graph
  2. Breadth-first search
  3. Depth-first search
  4. Bridges and articulation points
  5. Dijkstra's algorithm
  6. A*
  7. Bidirectional search
  8. The Bellman–Ford algorithm
  9. Floyd–Warshall algorithm
  10. Johnson's algorithm
  11. Contraction hierarchies
  12. Maximum flow
  13. Dinic's algorithm
  14. Graph colouring
  15. Bipartite matching
  16. Hungarian algorithm
  17. Eulerian path
  18. Lowest common ancestor
  19. Heavy paths
  20. Centroid decomposition

STR Strings and text

  1. Prefix function and KMP
  2. The Aho–Corasick algorithm
  3. Boyer–Moore
  4. The Burrows–Wheeler transform
  5. Manacher’s algorithm
  6. Edit distance
  7. BK-tree
  8. Huffman code
  9. LZ77 and LZW

DP Dynamic programming

  1. Dynamic programming
  2. The knapsack problem
  3. Longest increasing subsequence
  4. Longest common subsequence
  5. Interval DP
  6. Bitmask DP
  7. Digit DP
  8. Convex hull trick
  9. Knuth and divide-and-conquer optimisations

PAR Paradigms

  1. Greedy algorithms
  2. Backtracking
  3. Branch and bound
  4. Divide and conquer
  5. Closest pair of points
  6. Meet in the middle

NUM Numbers and algebra

  1. Euclid's algorithm
  2. Fast exponentiation
  3. Chinese remainder theorem
  4. Big integers
  5. Karatsuba multiplication
  6. Fast Fourier transform
  7. Matrices and recurrences
  8. Gaussian elimination

GEO Geometry

  1. Vectors and orientation
  2. Convex hull
  3. Delaunay triangulation and Voronoi diagram
  4. k-d tree

NUM~ Numerical methods

  1. Bisection and Newton's method
  2. Gradient descent
  3. Kahan summation

SYS Systems and big data

  1. Consistent hashing
  2. Count-Min Sketch
  3. HyperLogLog
  4. Inverted index and BM25
  5. LSM tree
  6. Retry with jitter
  7. CRDT
  8. LFU and ARC

ML Data and machine learning

  1. k-means
  2. DBSCAN
  3. k nearest neighbours
  4. Decision tree
  5. Linear regression
  6. Logistic regression
  7. Recommendations by matrix factorization
  8. Perceptron and backpropagation

RND Randomness

  1. Las Vegas and Monte Carlo
  2. Genetic algorithm

TEC Techniques

  1. Big O
  2. Fast input and output
  3. How to benchmark honestly
  4. Cache-friendly data
  5. Complexity from measurements
  6. Memoization
  7. Numbers worth knowing
  8. Amortized analysis
  9. Choosing a data structure
  10. How much memory it takes
  11. Coordinate compression
  12. Bit tricks