pub struct CountMinSketch<const WIDTH: usize, const DEPTH: usize> {
table: [[u32; WIDTH]; DEPTH],
seeds: [u64; DEPTH],
}
impl<const WIDTH: usize, const DEPTH: usize> CountMinSketch<WIDTH, DEPTH> {
pub fn new() -> Self {
let mut seeds = [0u64; DEPTH];
let mut state: u64 = 0xDEAD_BEEF_CAFE_BABE;
for seed in seeds.iter_mut() {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
*seed = state;
}
CountMinSketch {
table: [[0u32; WIDTH]; DEPTH],
seeds,
}
}
#[inline]
fn hash(&self, key: u64, row: usize) -> usize {
#[cfg(feature = "bcinr")]
{
let mut data = key.to_le_bytes().to_vec();
data.extend_from_slice(&self.seeds[row].to_le_bytes());
(crate::bcinr_compat::sketch::fnv1a_64(&data) as usize) % WIDTH
}
#[cfg(not(feature = "bcinr"))]
{
let seed = self.seeds[row];
let mut h = key.wrapping_mul(seed).wrapping_add(seed.rotate_left(17));
h ^= h >> 33;
h = h.wrapping_mul(0xff51afd7ed558ccd);
h ^= h >> 33;
h = h.wrapping_mul(0xc4ceb9fe1a85ec53);
h ^= h >> 33;
(h as usize) % WIDTH
}
}
#[inline]
pub fn add(&mut self, key: u64) {
for row in 0..DEPTH {
let bucket = self.hash(key, row);
self.table[row][bucket] = self.table[row][bucket].saturating_add(1);
}
}
#[inline]
pub fn add_count(&mut self, key: u64, count: u32) {
for row in 0..DEPTH {
let bucket = self.hash(key, row);
self.table[row][bucket] = self.table[row][bucket].saturating_add(count);
}
}
#[inline]
pub fn estimate(&self, key: u64) -> u32 {
(0..DEPTH)
.map(|row| self.table[row][self.hash(key, row)])
.min()
.unwrap_or(0)
}
#[inline]
pub fn add_pair(&mut self, from: u32, to: u32) {
#[cfg(feature = "bcinr")]
{
let mut data = from.to_le_bytes().to_vec();
data.extend_from_slice(&to.to_le_bytes());
self.add(crate::bcinr_compat::sketch::fnv1a_64(&data));
}
#[cfg(not(feature = "bcinr"))]
{
let mut key = from as u64;
key = key.wrapping_mul(0x9e3779b97f4a7c15);
key ^= key >> 30;
key ^= (to as u64).wrapping_mul(0xbf58476d1ce4e5b9);
key ^= key >> 27;
self.add(key);
}
}
#[inline]
pub fn estimate_pair(&self, from: u32, to: u32) -> u32 {
#[cfg(feature = "bcinr")]
{
let mut data = from.to_le_bytes().to_vec();
data.extend_from_slice(&to.to_le_bytes());
self.estimate(crate::bcinr_compat::sketch::fnv1a_64(&data))
}
#[cfg(not(feature = "bcinr"))]
{
let mut key = from as u64;
key = key.wrapping_mul(0x9e3779b97f4a7c15);
key ^= key >> 30;
key ^= (to as u64).wrapping_mul(0xbf58476d1ce4e5b9);
key ^= key >> 27;
self.estimate(key)
}
}
pub fn memory_bytes(&self) -> usize {
std::mem::size_of::<Self>()
}
}
impl<const WIDTH: usize, const DEPTH: usize> Default for CountMinSketch<WIDTH, DEPTH> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_key() {
let mut cms: CountMinSketch<4096, 8> = CountMinSketch::new();
cms.add(42);
assert_eq!(cms.estimate(42), 1);
}
#[test]
fn test_never_underestimates() {
let mut cms: CountMinSketch<4096, 8> = CountMinSketch::new();
for _ in 0..100 {
cms.add(1);
}
assert!(cms.estimate(1) >= 100);
}
#[test]
fn test_zero_for_missing_key() {
let cms: CountMinSketch<4096, 8> = CountMinSketch::new();
assert_eq!(cms.estimate(999), 0);
}
#[test]
fn test_error_rate_10k_items() {
let mut cms: CountMinSketch<4096, 8> = CountMinSketch::new();
for i in 0..10_000 {
let key = (i % 100) as u64; cms.add(key);
}
let mut max_error = 0.0f64;
for i in 0..100u64 {
let true_count = 100u32;
let est = cms.estimate(i);
let error = (est as f64 - true_count as f64) / true_count as f64;
max_error = max_error.max(error);
}
assert!(
max_error < 0.05,
"Max relative error {} exceeds 5% threshold",
max_error
);
}
#[test]
fn test_pair_operations() {
let mut cms: CountMinSketch<4096, 8> = CountMinSketch::new();
cms.add_pair(1, 2);
cms.add_pair(1, 2);
cms.add_pair(1, 3);
assert!(cms.estimate_pair(1, 2) >= 2);
assert!(cms.estimate_pair(1, 3) >= 1);
assert_eq!(cms.estimate_pair(2, 3), 0);
}
#[test]
fn test_memory_size() {
let cms: CountMinSketch<4096, 8> = CountMinSketch::new();
assert!(cms.memory_bytes() >= 4096 * 8 * 4);
}
#[test]
fn test_deterministic_hashes() {
let cms1: CountMinSketch<4096, 8> = CountMinSketch::new();
let cms2: CountMinSketch<4096, 8> = CountMinSketch::new();
for i in 0..100u64 {
assert_eq!(cms1.hash(i, 0), cms2.hash(i, 0));
assert_eq!(cms1.hash(i, 3), cms2.hash(i, 3));
}
}
}