use crate::common::{Sketch, SketchError};
use std::hash::Hasher;
use twox_hash::XxHash64;
#[derive(Debug, Clone, PartialEq)]
pub struct NitroSketchStats {
pub sample_rate: f64,
pub sampled_count: u64,
pub unsampled_count: u64,
pub total_items_estimated: u64,
}
pub struct NitroSketch<S: Sketch> {
base_sketch: S,
sample_rate: f64,
sampled_count: u64,
unsampled_count: u64,
}
impl<S: Sketch> NitroSketch<S> {
pub fn new(base_sketch: S, sample_rate: f64) -> Result<Self, SketchError> {
if sample_rate <= 0.0 || sample_rate > 1.0 {
return Err(SketchError::InvalidParameter {
param: "sample_rate".to_string(),
value: sample_rate.to_string(),
constraint: "must be in (0, 1]".to_string(),
});
}
Ok(NitroSketch {
base_sketch,
sample_rate,
sampled_count: 0,
unsampled_count: 0,
})
}
pub fn with_seed(base_sketch: S, sample_rate: f64, seed: u64) -> Result<Self, SketchError> {
if sample_rate <= 0.0 || sample_rate > 1.0 {
return Err(SketchError::InvalidParameter {
param: "sample_rate".to_string(),
value: sample_rate.to_string(),
constraint: "must be in (0, 1]".to_string(),
});
}
let mut nitro = NitroSketch {
base_sketch,
sample_rate,
sampled_count: seed, unsampled_count: 0,
};
nitro.sampled_count = 0;
Ok(nitro)
}
pub fn update_with_item<T>(&mut self, item: &T)
where
T: std::hash::Hash + ?Sized,
{
let mut hasher = XxHash64::with_seed(0);
item.hash(&mut hasher);
let hash = hasher.finish();
let threshold = (self.sample_rate * 10000.0) as u64;
let should_sample = (hash % 10000) < threshold;
if should_sample {
self.sampled_count += 1;
} else {
self.unsampled_count += 1;
}
}
pub fn update_sampled(&mut self, key: &[u8]) {
self.update_with_item(key);
}
pub fn query(&self, _key: &[u8]) -> u64 {
self.base_sketch.estimate() as u64
}
pub fn sync(&mut self, _unsampled_weight: f64) -> Result<(), SketchError> {
Ok(())
}
pub fn stats(&self) -> NitroSketchStats {
let total = self.sampled_count + self.unsampled_count;
NitroSketchStats {
sample_rate: self.sample_rate,
sampled_count: self.sampled_count,
unsampled_count: self.unsampled_count,
total_items_estimated: total,
}
}
pub fn base_sketch(&self) -> &S {
&self.base_sketch
}
pub fn base_sketch_mut(&mut self) -> &mut S {
&mut self.base_sketch
}
pub fn sample_rate(&self) -> f64 {
self.sample_rate
}
pub fn sampled_count(&self) -> u64 {
self.sampled_count
}
pub fn unsampled_count(&self) -> u64 {
self.unsampled_count
}
pub fn reset_stats(&mut self) {
self.sampled_count = 0;
self.unsampled_count = 0;
}
}
impl<S: Sketch> Sketch for NitroSketch<S> {
type Item = Vec<u8>;
fn update(&mut self, item: &Self::Item) {
self.update_sampled(item);
}
fn estimate(&self) -> f64 {
self.base_sketch.estimate()
}
fn is_empty(&self) -> bool {
self.sampled_count == 0 && self.unsampled_count == 0
}
fn serialize(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.sample_rate.to_le_bytes());
bytes.extend_from_slice(&self.sampled_count.to_le_bytes());
bytes.extend_from_slice(&self.unsampled_count.to_le_bytes());
bytes.extend_from_slice(&self.base_sketch.serialize());
bytes
}
fn deserialize(_bytes: &[u8]) -> Result<Self, SketchError>
where
Self: Sized,
{
Err(SketchError::DeserializationError(
"NitroSketch deserialization requires type-specific implementation".to_string(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frequency::CountMinSketch;
#[test]
fn test_construction() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let nitro = NitroSketch::new(base, 0.1);
assert!(nitro.is_ok());
}
#[test]
fn test_invalid_sample_rate_zero() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let result = NitroSketch::new(base, 0.0);
assert!(result.is_err());
}
#[test]
fn test_invalid_sample_rate_negative() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let result = NitroSketch::new(base, -0.1);
assert!(result.is_err());
}
#[test]
fn test_invalid_sample_rate_too_large() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let result = NitroSketch::new(base, 1.1);
assert!(result.is_err());
}
#[test]
fn test_update_sampled() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let mut nitro = NitroSketch::new(base, 0.1).unwrap();
nitro.update_sampled(b"test");
let stats = nitro.stats();
assert_eq!(stats.total_items_estimated, 1);
}
#[test]
fn test_stats() {
let base = CountMinSketch::new(0.01, 0.01).unwrap();
let mut nitro = NitroSketch::new(base, 0.5).unwrap();
for i in 0..100 {
nitro.update_sampled(format!("item_{}", i).as_bytes());
}
let stats = nitro.stats();
assert_eq!(stats.sample_rate, 0.5);
assert_eq!(stats.total_items_estimated, 100);
assert!(stats.sampled_count > 0);
assert!(stats.unsampled_count >= 0);
}
}