use crate::common::SketchError;
#[derive(Clone, Debug)]
struct Bucket {
timestamp: u64,
count: u64,
}
#[derive(Clone, Debug)]
pub struct SlidingWindowCounter {
buckets: Vec<Bucket>,
window_size: u64,
epsilon: f64,
k: usize,
total: u64,
}
impl SlidingWindowCounter {
pub fn new(window_size: u64, epsilon: f64) -> Result<Self, SketchError> {
if window_size == 0 {
return Err(SketchError::InvalidParameter {
param: "window_size".to_string(),
value: "0".to_string(),
constraint: "must be > 0".to_string(),
});
}
if epsilon <= 0.0 || epsilon >= 1.0 {
return Err(SketchError::InvalidParameter {
param: "epsilon".to_string(),
value: epsilon.to_string(),
constraint: "must be in (0, 1)".to_string(),
});
}
let k = (1.0 / epsilon).ceil() as usize;
Ok(SlidingWindowCounter {
buckets: Vec::new(),
window_size,
epsilon,
k,
total: 0,
})
}
pub fn window_size(&self) -> u64 {
self.window_size
}
pub fn epsilon(&self) -> f64 {
self.epsilon
}
pub fn increment(&mut self, timestamp: u64) {
self.increment_by(timestamp, 1);
}
pub fn increment_by(&mut self, timestamp: u64, count: u64) {
if count == 0 {
return;
}
self.total += count;
let mut remaining = count;
while remaining > 0 {
let power = 63 - remaining.leading_zeros();
let bucket_count = 1u64 << power;
self.buckets.insert(
0,
Bucket {
timestamp,
count: bucket_count,
},
);
remaining -= bucket_count;
}
self.merge_buckets();
}
fn merge_buckets(&mut self) {
if self.buckets.len() < 2 {
return;
}
let mut i = 0;
while i < self.buckets.len() {
let current_count = self.buckets[i].count;
let mut same_count = 1;
while i + same_count < self.buckets.len()
&& self.buckets[i + same_count].count == current_count
{
same_count += 1;
}
while same_count > self.k + 1 {
let merge_idx = i + same_count - 2;
let older_timestamp = self.buckets[merge_idx + 1].timestamp;
self.buckets[merge_idx] = Bucket {
timestamp: older_timestamp,
count: current_count * 2,
};
self.buckets.remove(merge_idx + 1);
same_count -= 1;
}
i += same_count;
}
}
pub fn count(&self, current_time: u64) -> u64 {
let window_start = current_time.saturating_sub(self.window_size);
let mut total = 0u64;
let mut last_partial = 0u64;
for bucket in &self.buckets {
if bucket.timestamp > current_time {
continue;
}
if bucket.timestamp >= window_start {
total += bucket.count;
} else {
last_partial = bucket.count / 2;
break;
}
}
total + last_partial
}
pub fn count_range(&self, start: u64, end: u64) -> u64 {
let mut total = 0u64;
for bucket in &self.buckets {
if bucket.timestamp > end {
continue;
}
if bucket.timestamp < start {
total += bucket.count / 2;
break;
}
total += bucket.count;
}
total
}
pub fn expire(&mut self, current_time: u64) {
let window_start = current_time.saturating_sub(self.window_size);
let mut found_outside = false;
self.buckets.retain(|bucket| {
if bucket.timestamp >= window_start {
true
} else if !found_outside {
found_outside = true;
true } else {
false
}
});
}
pub fn clear(&mut self) {
self.buckets.clear();
self.total = 0;
}
pub fn num_buckets(&self) -> usize {
self.buckets.len()
}
pub fn error_bound(&self) -> f64 {
self.epsilon
}
pub fn memory_usage(&self) -> usize {
std::mem::size_of::<Self>() + self.buckets.len() * std::mem::size_of::<Bucket>()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.window_size.to_le_bytes());
bytes.extend_from_slice(&self.epsilon.to_le_bytes());
bytes.extend_from_slice(&(self.k as u64).to_le_bytes());
bytes.extend_from_slice(&self.total.to_le_bytes());
bytes.extend_from_slice(&(self.buckets.len() as u64).to_le_bytes());
for bucket in &self.buckets {
bytes.extend_from_slice(&bucket.timestamp.to_le_bytes());
bytes.extend_from_slice(&bucket.count.to_le_bytes());
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SketchError> {
if bytes.len() < 40 {
return Err(SketchError::DeserializationError(
"Insufficient data for SlidingWindowCounter header".to_string(),
));
}
let window_size = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let epsilon = f64::from_le_bytes(bytes[8..16].try_into().unwrap());
let k = u64::from_le_bytes(bytes[16..24].try_into().unwrap()) as usize;
let total = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
let num_buckets = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
let expected_len = 40 + num_buckets * 16;
if bytes.len() < expected_len {
return Err(SketchError::DeserializationError(format!(
"Expected {} bytes, got {}",
expected_len,
bytes.len()
)));
}
let mut buckets = Vec::with_capacity(num_buckets);
let mut offset = 40;
for _ in 0..num_buckets {
let timestamp = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
let count = u64::from_le_bytes(bytes[offset + 8..offset + 16].try_into().unwrap());
buckets.push(Bucket { timestamp, count });
offset += 16;
}
Ok(SlidingWindowCounter {
buckets,
window_size,
epsilon,
k,
total,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let counter = SlidingWindowCounter::new(1000, 0.1).unwrap();
assert_eq!(counter.window_size(), 1000);
assert_eq!(counter.epsilon(), 0.1);
}
#[test]
fn test_invalid_params() {
assert!(SlidingWindowCounter::new(0, 0.1).is_err());
assert!(SlidingWindowCounter::new(1000, 0.0).is_err());
assert!(SlidingWindowCounter::new(1000, 1.0).is_err());
assert!(SlidingWindowCounter::new(1000, -0.1).is_err());
}
#[test]
fn test_increment_and_count() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment(10);
counter.increment(20);
counter.increment(30);
let count = counter.count(50);
assert!(count >= 2, "Expected at least 2, got {}", count);
}
#[test]
fn test_window_expiration() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment(10);
counter.increment(20);
counter.increment(200);
let count = counter.count(250);
assert!(count >= 1, "Expected at least 1, got {}", count);
}
#[test]
fn test_expire() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment(10);
counter.increment(20);
counter.increment(200);
let before = counter.num_buckets();
counter.expire(300);
let after = counter.num_buckets();
assert!(after <= before);
}
#[test]
fn test_increment_by() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment_by(10, 5);
counter.increment_by(20, 3);
let count = counter.count(50);
assert!(count >= 6, "Expected at least 6, got {}", count);
}
#[test]
fn test_count_range() {
let mut counter = SlidingWindowCounter::new(1000, 0.1).unwrap();
counter.increment(100);
counter.increment(200);
counter.increment(300);
counter.increment(400);
let count = counter.count_range(150, 350);
assert!(count >= 2, "Expected at least 2 in range, got {}", count);
}
#[test]
fn test_clear() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment(10);
counter.increment(20);
counter.clear();
assert_eq!(counter.count(50), 0);
assert_eq!(counter.num_buckets(), 0);
}
#[test]
fn test_serialization() {
let mut counter = SlidingWindowCounter::new(100, 0.1).unwrap();
counter.increment(10);
counter.increment(20);
counter.increment(30);
let bytes = counter.to_bytes();
let restored = SlidingWindowCounter::from_bytes(&bytes).unwrap();
assert_eq!(counter.window_size(), restored.window_size());
assert_eq!(counter.epsilon(), restored.epsilon());
assert_eq!(counter.num_buckets(), restored.num_buckets());
}
#[test]
fn test_bucket_merging() {
let mut counter = SlidingWindowCounter::new(1000, 0.5).unwrap();
for i in 0..20 {
counter.increment(i * 10);
}
assert!(
counter.num_buckets() < 20,
"Expected fewer buckets due to merging, got {}",
counter.num_buckets()
);
}
#[test]
fn test_accuracy() {
let epsilon = 0.1;
let mut counter = SlidingWindowCounter::new(1000, epsilon).unwrap();
let actual_count = 100;
for i in 0..actual_count {
counter.increment(i * 5);
}
let estimated = counter.count(500);
let error_ratio = (estimated as f64 - actual_count as f64).abs() / actual_count as f64;
assert!(
error_ratio <= epsilon + 0.1, "Error {} exceeds epsilon {}",
error_ratio,
epsilon
);
}
#[test]
fn test_memory_usage() {
let counter = SlidingWindowCounter::new(1000, 0.1).unwrap();
assert!(counter.memory_usage() > 0);
}
}