Choose items uniformly at random from a stream of unknown length, using memory and one pass.
Algorithm R — the classic
vector<int> reservoir(istream& in, int k) {
vector<int> res;
int x, i = 0;
while (in >> x) {
if (i < k) res.push_back(x);
else {
int j = uniform_int_distribution<int>(0, i)(rng);
if (j < k) res[j] = x;
}
i++;
}
return res;
}Keep the first ; for the -th item (, 0-indexed), keep it with probability , replacing a uniformly chosen slot.
Why it is uniform
Claim: after processing items, each is in the reservoir with probability .
Induction. True for . Suppose it holds after items. Item enters with probability by construction. An earlier item survives iff either the new item is rejected (probability ), or it is accepted but evicts a different slot (probability ). Total:
Variants
Single item ()
int chosen; int i = 0;
while (in >> x) { if (uniform_int_distribution<int>(0, i)(rng) == 0) chosen = x; i++; }Keep the -th item with probability . Two lines.
Weighted reservoir sampling (A-Res)
Give item the key with uniform in , and keep the largest keys in a min-heap.
priority_queue<pair<double,int>, vector<pair<double,int>>, greater<>> pq;
double key = pow(uniform_real_distribution<double>(0,1)(rng), 1.0 / w);
if ((int)pq.size() < k) pq.push({key, item});
else if (key > pq.top().first) { pq.pop(); pq.push({key, item}); }Selects each item with probability proportional to its weight, in one pass and memory.
Algorithm L — skip ahead
Instead of drawing a random number per item, compute how many items to skip before the next acceptance. Reduces the random-number count from to — the standard optimisation for very long streams.
Distributed sampling
Each machine keeps its own reservoir with a count; merge by sampling from the reservoirs weighted by their counts. Uniformity is preserved.
When it applies
| Situation | Why reservoir sampling |
|---|---|
| The input length is unknown in advance | cannot pre-allocate or index |
| The data does not fit in memory | space |
| A single pass is all you get | streaming |
| Sampling log lines, telemetry, network packets | the standard production use |
| Choosing a random element of a linked list | one traversal |
| Random node of a tree during a DFS | variant |
| Randomly selecting from a filtered subset | apply the filter as you stream |
If you do know the length
Simply pick random indices, or shuffle and take the first — simpler and faster. Reservoir sampling is specifically for the unknown-length or streaming case.
shuffle(v.begin(), v.end(), rng);
vector<int> sample(v.begin(), v.begin() + k);Or, for , sample indices with a hash set to avoid the full shuffle: expected.
Related streaming algorithms
| Problem | Algorithm | Space |
|---|---|---|
| Uniform sample of | reservoir sampling | |
| Count distinct elements | HyperLogLog | |
| Frequent items (“heavy hitters”) | Misra-Gries, Count-Min sketch | |
| Approximate quantiles | t-digest, GK summary | |
| Membership testing | Bloom filter | bits |
| Majority element | Boyer-Moore voting |
Boyer-Moore voting is the deterministic cousin worth pairing with this: it finds a strict majority element in one pass and space, with no randomness at all.
See also: Randomized Algorithms · Boyer-Moore Voting · Probability