pub fn sum<T>(values: &[T]) -> T
where
T: Default + Copy + core::ops::Add<Output = T>,
{
values.iter().fold(T::default(), |acc, &x| acc + x)
}
pub fn mean(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
sum(values) / values.len() as f64
}
pub fn variance(values: &[f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
let n = values.len() as f64;
let sum_x = sum(values);
let sum_x2: f64 = values.iter().map(|v| v * v).sum();
(sum_x2 / n) - (sum_x / n).powi(2)
}
pub fn std_dev(values: &[f64]) -> f64 {
variance(values).sqrt()
}
pub fn median(values: &mut [f64]) -> f64 {
if values.is_empty() {
return 0.0;
}
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = values.len() / 2;
if values.len() % 2 == 0 {
(values[mid - 1] + values[mid]) / 2.0
} else {
values[mid]
}
}
pub fn min_by<T, F, K>(items: &[T], f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord,
{
items.iter().min_by_key(|item| f(item))
}
pub fn max_by<T, F, K>(items: &[T], f: F) -> Option<&T>
where
F: Fn(&T) -> K,
K: Ord,
{
items.iter().max_by_key(|item| f(item))
}
pub fn percentile(values: &mut [f64], p: f64) -> f64 {
if values.is_empty() {
return 0.0;
}
let p = p.clamp(0.0, 100.0);
values.sort_by(|a, b| a.partial_cmp(b).unwrap());
if values.len() == 1 {
return values[0];
}
let pos = (p / 100.0) * ((values.len() - 1) as f64);
let idx = pos.round() as usize;
values[idx]
}