use core::cmp::Ordering::Equal;
use num_traits::Float;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum ScalingMethod {
MAR,
#[default]
MAD,
}
impl ScalingMethod {
pub fn compute<T: Float>(&self, vals: &mut [T]) -> T {
match self {
Self::MAR => self.compute_mar(vals),
Self::MAD => self.compute_mad(vals),
}
}
#[inline]
fn compute_mad<T: Float>(&self, vals: &mut [T]) -> T {
if vals.is_empty() {
return T::zero();
}
let median: T = Self::median_inplace(vals);
for val in vals.iter_mut() {
*val = (*val - median).abs();
}
Self::median_inplace(vals)
}
#[inline]
fn compute_mar<T: Float>(&self, vals: &mut [T]) -> T {
if vals.is_empty() {
return T::zero();
}
for val in vals.iter_mut() {
*val = val.abs();
}
Self::median_inplace(vals)
}
#[inline]
fn median_inplace<T: Float>(vals: &mut [T]) -> T {
let n = vals.len();
if n == 0 {
return T::zero();
}
let mid = n / 2;
if n % 2 == 0 {
vals.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap_or(Equal));
let upper = vals[mid];
let lower = vals[..mid].iter().copied().fold(T::neg_infinity(), T::max);
(lower + upper) / T::from(2.0).unwrap()
} else {
vals.select_nth_unstable_by(mid, |a, b| a.partial_cmp(b).unwrap_or(Equal));
vals[mid]
}
}
}