#[derive(Debug, Clone)]
pub struct Reservoir {
row_ids: Vec<u64>,
k: usize,
seen: u64,
state: u64,
}
impl Reservoir {
pub fn new(k: usize, seed: u64) -> Self {
Self {
row_ids: Vec::with_capacity(k),
k,
seen: 0,
state: seed,
}
}
pub fn capacity(&self) -> usize {
self.k
}
pub fn row_ids(&self) -> &[u64] {
&self.row_ids
}
pub fn seen(&self) -> u64 {
self.seen
}
pub fn offer(&mut self, rid: u64) {
self.seen += 1;
if (self.row_ids.len() as u64) < self.k as u64 {
self.row_ids.push(rid);
} else {
let j = self.gen_range_u64(0, self.seen);
if (j as usize) < self.k {
self.row_ids[j as usize] = rid;
}
}
}
pub fn reset(&mut self) {
self.row_ids.clear();
self.seen = 0;
}
fn gen_range_u64(&mut self, lo: u64, hi: u64) -> u64 {
let n = hi.saturating_sub(lo);
if n == 0 {
return lo;
}
lo + (self.next_u64() % n)
}
fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
}
impl Default for Reservoir {
fn default() -> Self {
Self::new(8192, 0xC0FF_BEEF_1234_5678)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_then_reservoirs() {
let mut r = Reservoir::new(8, 1);
for i in 0..10_000u64 {
r.offer(i);
}
assert_eq!(r.row_ids.len(), 8);
assert!(r.row_ids.iter().all(|&x| x < 10_000));
assert_eq!(r.seen, 10_000);
}
#[test]
fn small_table_is_exact() {
let mut r = Reservoir::new(8192, 2);
for i in 0..50u64 {
r.offer(i);
}
let mut sorted = r.row_ids.to_vec();
sorted.sort_unstable();
assert_eq!(sorted, (0..50).collect::<Vec<_>>());
}
#[test]
fn is_uniform_ish() {
let mut r = Reservoir::new(4096, 7);
for i in 0..100_000u64 {
r.offer(i);
}
let mean: f64 = r.row_ids.iter().map(|&x| x as f64).sum::<f64>() / r.row_ids.len() as f64;
assert!((mean - 49_999.5).abs() < 2_000.0, "mean {mean} biased");
}
}