use rand::{Rng, RngExt};
pub const LOC_BUCKETS: &[(usize, &str)] = &[
(10, "0-10"),
(30, "10-30"),
(100, "30-100"),
(300, "100-300"),
(1_000, "300-1000"),
(3_000, "1000-3000"),
(usize::MAX, "3000+"),
];
pub fn loc_bucket(loc: usize) -> &'static str {
LOC_BUCKETS
.iter()
.find(|(upper, _)| loc < *upper)
.map(|(_, name)| *name)
.unwrap_or("3000+")
}
pub struct Reservoir<T> {
pub items: Vec<T>,
seen: u64,
}
impl<T> Default for Reservoir<T> {
fn default() -> Self {
Self {
items: Vec::new(),
seen: 0,
}
}
}
impl<T> Reservoir<T> {
pub fn offer(&mut self, item: T, capacity: usize, rng: &mut impl Rng) {
self.seen += 1;
if self.items.len() < capacity {
self.items.push(item);
} else {
let j = rng.random_range(0..self.seen) as usize;
if j < capacity {
self.items[j] = item;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
use rand::rngs::StdRng;
#[test]
fn reservoir_never_exceeds_capacity() {
let mut rng = StdRng::seed_from_u64(7);
let mut reservoir = Reservoir::default();
for i in 0..1000u32 {
reservoir.offer(i, 5, &mut rng);
}
assert_eq!(reservoir.items.len(), 5);
assert_eq!(reservoir.seen, 1000);
}
#[test]
fn reservoir_keeps_everything_below_capacity() {
let mut rng = StdRng::seed_from_u64(7);
let mut reservoir = Reservoir::default();
for i in 0..3u32 {
reservoir.offer(i, 5, &mut rng);
}
assert_eq!(reservoir.items, vec![0, 1, 2]);
}
#[test]
fn loc_bucket_boundaries() {
assert_eq!(loc_bucket(0), "0-10");
assert_eq!(loc_bucket(9), "0-10");
assert_eq!(loc_bucket(10), "10-30");
assert_eq!(loc_bucket(29), "10-30");
assert_eq!(loc_bucket(30), "30-100");
assert_eq!(loc_bucket(99), "30-100");
assert_eq!(loc_bucket(100), "100-300");
assert_eq!(loc_bucket(299), "100-300");
assert_eq!(loc_bucket(300), "300-1000");
assert_eq!(loc_bucket(999), "300-1000");
assert_eq!(loc_bucket(1_000), "1000-3000");
assert_eq!(loc_bucket(2_999), "1000-3000");
assert_eq!(loc_bucket(3_000), "3000+");
assert_eq!(loc_bucket(usize::MAX), "3000+");
}
}