use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReqMode {
HighRankAccuracy,
LowRankAccuracy,
}
#[derive(Debug, Clone)]
struct Compactor {
items: Vec<f64>,
capacity: usize,
level: usize,
}
impl Compactor {
fn new(capacity: usize, level: usize) -> Self {
Self {
items: Vec::with_capacity(capacity),
capacity,
level,
}
}
fn is_full(&self) -> bool {
self.items.len() >= self.capacity
}
fn compact(&mut self, mode: ReqMode) -> Vec<f64> {
self.items
.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
match mode {
ReqMode::HighRankAccuracy => {
let compacted: Vec<f64> = self.items.iter().step_by(2).copied().collect();
self.items.clear();
compacted
}
ReqMode::LowRankAccuracy => {
let compacted: Vec<f64> = self.items.iter().skip(1).step_by(2).copied().collect();
self.items.clear();
compacted
}
}
}
}
#[derive(Debug, Clone)]
pub struct ReqSketch {
k: usize,
mode: ReqMode,
compactors: Vec<Compactor>,
n: u64,
min: Option<f64>,
max: Option<f64>,
}
impl ReqSketch {
pub fn new(k: usize, mode: ReqMode) -> Result<Self, String> {
if !(4..=1024).contains(&k) {
return Err(format!("k must be in range [4, 1024], got {}", k));
}
let compactors = vec![Compactor::new(2 * k, 0)];
Ok(Self {
k,
mode,
compactors,
n: 0,
min: None,
max: None,
})
}
pub fn n(&self) -> u64 {
self.n
}
pub fn is_empty(&self) -> bool {
self.n == 0
}
pub fn min(&self) -> Option<f64> {
self.min
}
pub fn max(&self) -> Option<f64> {
self.max
}
pub fn update(&mut self, value: f64) {
self.n += 1;
self.min = Some(self.min.map_or(value, |m| m.min(value)));
self.max = Some(self.max.map_or(value, |m| m.max(value)));
self.compactors[0].items.push(value);
self.propagate_compactions();
}
fn propagate_compactions(&mut self) {
let mut level = 0;
while level < self.compactors.len() && self.compactors[level].is_full() {
let promoted = self.compactors[level].compact(self.mode);
if level + 1 >= self.compactors.len() {
self.compactors.push(Compactor::new(2 * self.k, level + 1));
}
self.compactors[level + 1].items.extend(promoted);
level += 1;
}
}
pub fn quantile(&self, q: f64) -> Option<f64> {
if !(0.0..=1.0).contains(&q) || q.is_nan() {
return None;
}
if self.is_empty() {
return None;
}
match self.mode {
ReqMode::HighRankAccuracy if q == 1.0 => return self.max,
ReqMode::LowRankAccuracy if q == 0.0 => return self.min,
_ => {}
}
if q == 0.0 {
return self.min;
}
if q == 1.0 {
return self.max;
}
let mut weighted_items = Vec::new();
for compactor in &self.compactors {
let weight = 1u64 << compactor.level; for &item in &compactor.items {
weighted_items.push((item, weight));
}
}
weighted_items.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
let target_rank = (q * self.n as f64) as u64;
let mut cumulative_weight = 0u64;
for (value, weight) in weighted_items {
cumulative_weight += weight;
if cumulative_weight >= target_rank {
return Some(value);
}
}
self.max
}
pub fn merge(&self, other: &Self) -> Result<Self, String> {
if self.k != other.k {
return Err(format!(
"Cannot merge sketches with different k values: {} vs {}",
self.k, other.k
));
}
if self.mode != other.mode {
return Err(format!(
"Cannot merge sketches with different modes: {:?} vs {:?}",
self.mode, other.mode
));
}
if self.is_empty() {
return Ok(other.clone());
}
if other.is_empty() {
return Ok(self.clone());
}
let mut merged = Self::new(self.k, self.mode).unwrap();
merged.min = Some(self.min.unwrap().min(other.min.unwrap()));
merged.max = Some(self.max.unwrap().max(other.max.unwrap()));
merged.n = self.n + other.n;
let mut all_items = Vec::new();
for compactor in &self.compactors {
let weight = 1u64 << compactor.level;
for &item in &compactor.items {
all_items.push((item, weight, compactor.level));
}
}
for compactor in &other.compactors {
let weight = 1u64 << compactor.level;
for &item in &compactor.items {
all_items.push((item, weight, compactor.level));
}
}
all_items.sort_by_key(|(_, _, level)| *level);
for (item, weight, level) in all_items {
while level >= merged.compactors.len() {
let new_level = merged.compactors.len();
merged
.compactors
.push(Compactor::new(2 * merged.k, new_level));
}
for _ in 0..weight {
merged.compactors[level].items.push(item);
}
}
for level in 0..merged.compactors.len() {
while merged.compactors[level].is_full() {
let promoted = merged.compactors[level].compact(merged.mode);
if level + 1 >= merged.compactors.len() {
merged
.compactors
.push(Compactor::new(2 * merged.k, level + 1));
}
merged.compactors[level + 1].items.extend(promoted);
}
}
Ok(merged)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compactor_hra_preserves_max() {
let mut compactor = Compactor::new(10, 0);
compactor.items = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let compacted = compactor.compact(ReqMode::HighRankAccuracy);
assert!(compacted.contains(&6.0) || compacted.contains(&5.0));
}
#[test]
fn test_mode_equality() {
assert_eq!(ReqMode::HighRankAccuracy, ReqMode::HighRankAccuracy);
assert_ne!(ReqMode::HighRankAccuracy, ReqMode::LowRankAccuracy);
}
}