Sampling and summarizing data that arrives as a stream: unknown length, one pass, memory far smaller than the data. If you do know the length and have the data, simply shuffle and take the first , or pick random indices.

ProblemAlgorithmSpaceGuarantee
uniform sample of reservoir sampling (Algorithm R; Algorithm L skips ahead)exact uniformity
weighted sample of (without replacement)A-Res: keep the largest keys exact
distinct indices from Floyd’s algorithmexact
frequent items (heavy hitters)Misra–Gries ( counters)undercounts by
frequency of any itemCount–Min sketchovercounts by w.p.
membershipBloom filter bitsno false negatives; false positives
count distinct elementsHyperLogLog small registers ( bits)relative error
approximate quantilest-digest, GK summaryrank error
majority elementBoyer–Moore votingexact if a strict majority exists: Classic Array Problems
When reservoir sampling appliesWhy
input length unknown in advancecannot pre-allocate or index
data does not fit in memory, single pass space, streaming
sampling log lines, telemetry, network packetsthe standard production use
random element of a linked list, random node during a DFSone traversal,
random element of a filtered subsetapply the filter while streaming
distributed dataeach machine keeps a reservoir and its count; merge by sampling from the reservoirs weighted by their counts

Reservoir sampling and exact samplers

template<class T> vector<T> reservoirR(const vector<T>& stream, int k, mt19937_64& rng) {   // Algorithm R
    vector<T> res;
    for (ll i = 0; i < sz(stream); i++) {
        if (i < k) res.push_back(stream[i]);
        else { ll j = uniform_int_distribution<ll>(0, i)(rng); if (j < k) res[j] = stream[i]; }   // keep w.p. k/(i+1)
    }
    return res;
}
 
template<class T> vector<T> reservoirL(const vector<T>& stream, int k, mt19937_64& rng) {   // Algorithm L (Li)
    uniform_real_distribution<double> U(0, 1); int n = sz(stream);
    vector<T> res(stream.begin(), stream.begin() + min(k, n));
    if (n <= k) return res;
    auto u = [&] { double x; do x = U(rng); while (x == 0); return x; };
    double W = exp(log(u()) / k); ll i = k - 1;
    while (true) {
        i += (ll)floor(log(u()) / log1p(-W)) + 1;    // skip a geometric number of items
        if (i >= n) break;
        res[rng() % k] = stream[i];
        W *= exp(log(u()) / k);
    }
    return res;
}
 
vector<int> weightedReservoir(const vector<double>& w, int k, mt19937_64& rng) {   // A-Res: indices, prob. ~ weight
    uniform_real_distribution<double> U(0, 1);
    priority_queue<pair<double, int>, vector<pair<double, int>>, greater<>> pq;   // min-heap of keys
    for (int i = 0; i < sz(w); i++) {
        double key = pow(U(rng), 1.0 / w[i]);
        if (sz(pq) < k) pq.push({key, i}); else if (key > pq.top().first) pq.pop(), pq.push({key, i});
    }
    vector<int> res; while (!pq.empty()) res.push_back(pq.top().second), pq.pop();
    return res;
}
 
vector<ll> floydSample(ll n, int k, mt19937_64& rng) {   // k distinct uniform values of [0, n), O(k) expected
    unordered_set<ll> S;
    for (ll j = n - k; j < n; j++) { ll t = uniform_int_distribution<ll>(0, j)(rng); if (!S.insert(t).second) S.insert(j); }
    return vector<ll>(all(S));
}

Sketches

ull splitmix(ull x) { x += 0x9e3779b97f4a7c15ULL; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL; return x ^ (x >> 31); }
 
struct MisraGries {                                      // heavy hitters with k counters
    int k; map<ll, ll> c;
    MisraGries(int k) : k(k) {}
    void add(ll x) {
        if (c.count(x) || sz(c) < k) { c[x]++; return; }
        for (auto it = c.begin(); it != c.end();) { if (--it->second == 0) it = c.erase(it); else ++it; }   // decrement all
    }
    ll estimate(ll x) const { auto it = c.find(x); return it == c.end() ? 0 : it->second; }   // f - n/(k+1) <= est <= f
};
 
struct CountMin {                                        // width w ~ e / eps, depth d ~ ln(1 / delta)
    int w, d; vector<vector<ll>> t; vector<ull> seed;
    CountMin(int w, int d, ull s) : w(w), d(d), t(d, vector<ll>(w, 0)), seed(d) { for (int i = 0; i < d; i++) seed[i] = splitmix(s + i); }
    void add(ll x, ll c = 1) { for (int i = 0; i < d; i++) t[i][splitmix(x ^ seed[i]) % w] += c; }
    ll estimate(ll x) const { ll r = LLONG_MAX; for (int i = 0; i < d; i++) r = min(r, t[i][splitmix(x ^ seed[i]) % w]); return r; }
};
 
struct Bloom {                                           // m bits, k hash functions (double hashing)
    int m, k; vector<ull> bits; ull s;
    Bloom(int m, int k, ull s) : m(m), k(k), bits((m + 63) / 64), s(s) {}
    void add(ll x) { ull h1 = splitmix(x ^ s), h2 = splitmix(h1) | 1; for (int i = 0; i < k; i++) { ull b = (h1 + i * h2) % m; bits[b >> 6] |= 1ULL << (b & 63); } }
    bool maybe(ll x) const { ull h1 = splitmix(x ^ s), h2 = splitmix(h1) | 1; for (int i = 0; i < k; i++) { ull b = (h1 + i * h2) % m; if (!(bits[b >> 6] >> (b & 63) & 1)) return false; } return true; }
};
 
struct HyperLogLog {                                     // m = 2^b registers
    int b, m; vector<uint8_t> reg; ull s;
    HyperLogLog(int b, ull s) : b(b), m(1 << b), reg(1 << b, 0), s(s) {}
    void add(ll x) { ull h = splitmix(x ^ s); int idx = h >> (64 - b); ull rest = h << b; int r = rest ? __builtin_clzll(rest) + 1 : 64 - b + 1; reg[idx] = max<int>(reg[idx], r); }
    double estimate() const {
        double sum = 0; int zeros = 0;
        for (int r : reg) sum += ldexp(1.0, -r), zeros += r == 0;
        double alpha = 0.7213 / (1 + 1.079 / m), E = alpha * m * m / sum;
        if (E <= 2.5 * m && zeros) E = m * log((double)m / zeros);   // small-range correction (linear counting)
        return E;
    }
};