use crate::common::{Mergeable, SketchError};
use crate::frequency::CountMinSketch;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use twox_hash::XxHash64;
#[derive(Clone, Debug)]
pub struct RemovableUniversalSketch {
cms: CountMinSketch,
#[allow(dead_code)]
deletions: HashMap<Vec<u8>, i64>,
moment_sketch: Vec<i64>,
depth: usize,
width: usize,
mask: usize,
}
impl RemovableUniversalSketch {
pub fn new(epsilon: f64, delta: f64) -> Result<Self, SketchError> {
let cms = CountMinSketch::new(epsilon, delta)?;
let width = cms.width();
let depth = cms.depth();
let mask = width - 1;
let moment_sketch = vec![0i64; depth * width];
Ok(RemovableUniversalSketch {
cms,
deletions: HashMap::new(),
moment_sketch,
depth,
width,
mask,
})
}
pub fn update<T: Hash>(&mut self, item: &T, delta: i32) {
if delta == 0 {
return;
}
if delta > 0 {
for _ in 0..delta {
self.cms.update(item);
}
}
let mut hasher = XxHash64::with_seed(0);
item.hash(&mut hasher);
let width = self.width;
let mask = self.mask;
let depth = self.depth;
let delta_i64 = delta as i64;
for row_idx in 0..depth {
let hash = hasher.finish();
let col_idx = (hash as usize) & mask;
let idx = row_idx * width + col_idx;
let sign = if (hash >> 32) & 1 == 0 { 1 } else { -1 };
unsafe {
*self.moment_sketch.get_unchecked_mut(idx) += sign * delta_i64;
}
hasher.write(&[0x7B]);
}
}
pub fn estimate<T: Hash>(&self, item: &T) -> i64 {
self.cms.estimate(item) as i64
}
pub fn l2_norm(&self) -> f64 {
if self.depth == 0 {
return 0.0;
}
let mut l2_estimates = Vec::with_capacity(self.depth);
for row_idx in 0..self.depth {
let start = row_idx * self.width;
let end = start + self.width;
let row = &self.moment_sketch[start..end];
let sum_squares: i64 = row.iter().map(|&x| x * x).sum();
let estimate = (sum_squares as f64).sqrt();
l2_estimates.push(estimate);
}
if l2_estimates.is_empty() {
0.0
} else {
l2_estimates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
l2_estimates[l2_estimates.len() / 2]
}
}
pub fn epsilon(&self) -> f64 {
self.cms.epsilon()
}
pub fn delta(&self) -> f64 {
self.cms.delta()
}
pub fn width(&self) -> usize {
self.width
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn merge(&mut self, other: &RemovableUniversalSketch) -> Result<(), SketchError> {
self.cms.merge(&other.cms)?;
for (self_val, &other_val) in self
.moment_sketch
.iter_mut()
.zip(other.moment_sketch.iter())
{
*self_val = self_val.saturating_add(other_val);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rus_new() {
let rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
assert!(rus.width() > 0);
assert!(rus.depth() > 0);
}
#[test]
fn test_rus_insert_basic() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item", 100);
let freq = rus.estimate(&"item");
assert!(freq >= 100);
}
#[test]
fn test_rus_insert_and_delete() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item", 100);
rus.update(&"item", -30);
let freq = rus.estimate(&"item");
assert!(freq >= 70);
}
#[test]
fn test_rus_negative_frequency() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item", 50);
rus.update(&"item", -100);
let freq = rus.estimate(&"item");
assert!(freq >= 0);
}
#[test]
fn test_rus_multiple_items() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item1", 100);
rus.update(&"item2", 50);
rus.update(&"item3", 25);
let freq1 = rus.estimate(&"item1");
let freq2 = rus.estimate(&"item2");
let freq3 = rus.estimate(&"item3");
assert!(freq1 >= 100);
assert!(freq2 >= 50);
assert!(freq3 >= 25);
}
#[test]
fn test_rus_turnstile_stream() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item", 100);
assert!(rus.estimate(&"item") >= 100);
rus.update(&"item", -50);
let freq_after_delete = rus.estimate(&"item");
assert!(freq_after_delete >= 50);
rus.update(&"item", 100);
let freq_final = rus.estimate(&"item");
assert!(freq_final >= 150);
}
#[test]
fn test_rus_l2_norm() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item1", 100);
rus.update(&"item2", 50);
rus.update(&"item3", 25);
let l2 = rus.l2_norm();
assert!(l2 > 0.0);
assert!(l2 >= 100.0);
}
#[test]
fn test_rus_l2_norm_with_deletions() {
let mut rus = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus.update(&"item1", 100);
rus.update(&"item2", 50);
let l2_before = rus.l2_norm();
rus.update(&"item1", -50);
rus.update(&"item2", -25);
let l2_after = rus.l2_norm();
assert!(l2_after <= l2_before);
}
#[test]
fn test_rus_merge() {
let mut rus1 = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
let mut rus2 = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus1.update(&"item", 100);
rus2.update(&"item", 50);
rus1.merge(&rus2).unwrap();
let freq = rus1.estimate(&"item");
assert!(freq >= 150);
}
#[test]
fn test_rus_merge_with_deletions() {
let mut rus1 = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
let mut rus2 = RemovableUniversalSketch::new(0.01, 0.01).unwrap();
rus1.update(&"item", 100);
rus1.update(&"item", -20);
rus2.update(&"item", 50);
rus2.update(&"item", -10);
rus1.merge(&rus2).unwrap();
let freq = rus1.estimate(&"item");
assert!(freq >= 120);
}
}