use num_traits::ToPrimitive;
use crate::{
Index, ShallowClone,
core::{Insert, Remove, Seal, Update},
};
#[derive(Debug, Clone, Copy)]
pub struct StdDev<T> {
mean: f64,
sum_sq_diff: f64,
count: u64,
_phantom: core::marker::PhantomData<T>,
}
impl<T> Default for StdDev<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> StdDev<T> {
pub fn new() -> Self {
StdDev {
mean: 0.0,
sum_sq_diff: 0.0,
count: 0,
_phantom: core::marker::PhantomData,
}
}
}
impl<T> Index<T> for StdDev<T>
where
T: ToPrimitive + Copy + 'static,
{
#[inline]
fn insert(&mut self, _seal: Seal, op: &Insert<T>) {
if let Some(x) = op.new.to_f64() {
self.count += 1;
let k = self.count;
let old_mean = self.mean;
self.mean = old_mean + (x - old_mean) / k as f64;
self.sum_sq_diff += (x - old_mean) * (x - self.mean);
}
}
#[inline]
fn remove(&mut self, _seal: Seal, op: &Remove<T>) {
if let Some(x) = op.existing.to_f64() {
let n = self.count;
if n <= 1 {
self.mean = 0.0;
self.sum_sq_diff = 0.0;
self.count = 0;
return;
}
let old_mean = self.mean;
self.mean = (n as f64 * old_mean - x) / (n - 1) as f64;
self.sum_sq_diff -= (x - old_mean) * (x - self.mean);
self.sum_sq_diff = self.sum_sq_diff.max(0.0);
self.count = n - 1;
}
}
#[inline]
fn update(&mut self, _seal: Seal, op: &Update<T>) {
if let (Some(old_val), Some(new_val)) = (op.existing.to_f64(), op.new.to_f64()) {
let n = self.count;
if n == 0 {
return;
}
if n == 1 {
self.mean = new_val;
self.sum_sq_diff = 0.0;
return;
}
let old_mean = self.mean;
let mean_without_old = (n as f64 * old_mean - old_val) / (n - 1) as f64;
let sum_sq_diff_without_old =
self.sum_sq_diff - (old_val - old_mean) * (old_val - mean_without_old);
let new_mean = mean_without_old + (new_val - mean_without_old) / n as f64;
let new_sum_sq_diff =
sum_sq_diff_without_old + (new_val - mean_without_old) * (new_val - new_mean);
self.mean = new_mean;
self.sum_sq_diff = new_sum_sq_diff.max(0.0);
}
}
}
impl<T> StdDev<T> {
#[inline]
pub fn get(&self) -> f64 {
if self.count < 2 {
return 0.0;
}
(self.sum_sq_diff / (self.count - 1) as f64).sqrt()
}
}
impl<T: Clone> ShallowClone for StdDev<T> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_std_dev_basic() {
use crate::core::Collection;
let mut db = Collection::new(StdDev::<f64>::new());
let calculate_std_dev = |collection: &Collection<f64, _>| -> f64 {
let values: Vec<f64> = collection.iter().into_iter().map(|(_, &v)| v).collect();
if values.len() < 2 {
return 0.0;
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values
.iter()
.map(|&x| {
let diff = x - mean;
diff * diff
})
.sum::<f64>()
/ (values.len() - 1) as f64;
variance.sqrt()
};
assert_eq!(db.query(|ix| ix.get()), calculate_std_dev(&db));
let _k1 = db.insert(5.0);
assert_eq!(db.query(|ix| ix.get()), calculate_std_dev(&db));
let k2 = db.insert(10.0);
let expected = calculate_std_dev(&db);
let result = db.query(|ix| ix.get());
assert!((result - expected).abs() < 1e-10);
let k3 = db.insert(15.0);
let expected = calculate_std_dev(&db);
let result = db.query(|ix| ix.get());
assert!((result - expected).abs() < 1e-10);
db.delete_by_key(k3);
let expected = calculate_std_dev(&db);
let result = db.query(|ix| ix.get());
assert!((result - expected).abs() < 1e-10);
db.delete_by_key(k2);
assert_eq!(db.query(|ix| ix.get()), calculate_std_dev(&db));
}
}