use super::{AggregateChangeSets, AggregateType, StdDev};
use ferroui_reactive::ObservableExt;
use ferroui_system::IObservable;
use std::rc::Rc;
pub trait StdDevValue: Copy + 'static {
type Moment: Copy + 'static;
const ZERO: Self::Moment;
fn moment(self) -> Self::Moment;
fn plus(left: Self::Moment, right: Self::Moment) -> Self::Moment;
fn minus(left: Self::Moment, right: Self::Moment) -> Self::Moment;
fn times(left: Self::Moment, right: Self::Moment) -> Self::Moment;
fn deviation(count: i32, sum_of_items: Self::Moment, sum_of_squares: Self::Moment) -> f64;
fn to_f64(self) -> f64;
}
fn integral_std_dev(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
let count = i128::from(count);
let sum_of_items = i128::from(sum_of_items);
let scaled_sum_of_squared_deviations = i128::from(sum_of_squares) * count - sum_of_items * sum_of_items;
let divisor = count * (count - 1);
let quotient = scaled_sum_of_squared_deviations / divisor;
let remainder = scaled_sum_of_squared_deviations % divisor;
(quotient as f64 + remainder as f64 / divisor as f64).sqrt()
}
impl StdDevValue for i32 {
type Moment = i64;
const ZERO: i64 = 0;
fn moment(self) -> i64 {
i64::from(self)
}
fn plus(left: i64, right: i64) -> i64 {
left.wrapping_add(right)
}
fn minus(left: i64, right: i64) -> i64 {
left.wrapping_sub(right)
}
fn times(left: i64, right: i64) -> i64 {
left.wrapping_mul(right)
}
fn deviation(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
integral_std_dev(count, sum_of_items, sum_of_squares)
}
fn to_f64(self) -> f64 {
f64::from(self)
}
}
impl StdDevValue for i64 {
type Moment = i64;
const ZERO: i64 = 0;
fn moment(self) -> i64 {
self
}
fn plus(left: i64, right: i64) -> i64 {
left.wrapping_add(right)
}
fn minus(left: i64, right: i64) -> i64 {
left.wrapping_sub(right)
}
fn times(left: i64, right: i64) -> i64 {
left.wrapping_mul(right)
}
fn deviation(count: i32, sum_of_items: i64, sum_of_squares: i64) -> f64 {
integral_std_dev(count, sum_of_items, sum_of_squares)
}
fn to_f64(self) -> f64 {
self as f64
}
}
impl StdDevValue for f64 {
type Moment = f64;
const ZERO: f64 = 0.0;
fn moment(self) -> f64 {
self
}
fn plus(left: f64, right: f64) -> f64 {
left + right
}
fn minus(left: f64, right: f64) -> f64 {
left - right
}
fn times(left: f64, right: f64) -> f64 {
left * right
}
fn deviation(count: i32, sum_of_items: f64, sum_of_squares: f64) -> f64 {
let count = f64::from(count);
((sum_of_squares - (sum_of_items * sum_of_items) / count) / (count - 1.0)).sqrt()
}
fn to_f64(self) -> f64 {
self
}
}
impl StdDevValue for f32 {
type Moment = f32;
const ZERO: f32 = 0.0;
fn moment(self) -> f32 {
self
}
fn plus(left: f32, right: f32) -> f32 {
left + right
}
fn minus(left: f32, right: f32) -> f32 {
left - right
}
fn times(left: f32, right: f32) -> f32 {
left * right
}
fn deviation(count: i32, sum_of_items: f32, sum_of_squares: f32) -> f64 {
let variance = (sum_of_squares - (sum_of_items * sum_of_items) / count as f32) / (count - 1) as f32;
f64::from(variance).sqrt()
}
fn to_f64(self) -> f64 {
f64::from(self)
}
}
pub(crate) fn std_dev<T: 'static, V: StdDevValue>(source: AggregateChangeSets<T>, value_selector: impl Fn(&T) -> V + 'static, fallback_value: V) -> Rc<dyn IObservable<f64>> {
let fallback_value = fallback_value.to_f64();
source
.scan_with(StdDev::new(0, V::ZERO, V::ZERO), move |state: StdDev<V::Moment>, changes| {
changes.items().fold(state, |current, aggregate_item| {
let item = value_selector(aggregate_item.item()).moment();
let square = V::times(item, item);
match aggregate_item.aggregate_type() {
AggregateType::Add => {
StdDev::new(current.count() + 1, V::plus(current.sum_of_items(), item), V::plus(current.sum_of_squares(), square))
}
AggregateType::Remove => {
StdDev::new(current.count() - 1, V::minus(current.sum_of_items(), item), V::minus(current.sum_of_squares(), square))
}
}
})
})
.select(move |values: StdDev<V::Moment>| {
if values.count() < 2 {
fallback_value
} else {
V::deviation(values.count(), values.sum_of_items(), values.sum_of_squares())
}
})
}