downsample 0.0.6

keep downsampled history of data over long period of time
Documentation
//! Built-in reducer constructors for common aggregation policies.
//!
//! Available reducers include:
//! - averages for floats and common integer widths,
//! - min/max reducers for partially ordered values,
//! - median reducers for ordered values and floats,
//! - first/last reducers for selecting a window boundary.

use super::Reducer;
use alloc::vec::Vec;

pub fn first<T>() -> Reducer<T>
where
    T: Copy + Default,
{
    Reducer::new(first_impl::<T>)
}

pub fn last<T>() -> Reducer<T>
where
    T: Copy + Default,
{
    Reducer::new(last_impl::<T>)
}

pub fn min<T>() -> Reducer<T>
where
    T: Copy + Default + PartialOrd,
{
    Reducer::new(min_impl::<T>)
}

pub fn max<T>() -> Reducer<T>
where
    T: Copy + Default + PartialOrd,
{
    Reducer::new(max_impl::<T>)
}

/// Returns a reducer that computes the lower median.
///
/// For even-sized windows this returns the lower of the two middle values.
/// The implementation copies the aggregation window into a temporary `Vec`
/// and sorts it.
pub fn median<T>() -> Reducer<T>
where
    T: Copy + Default + Ord,
{
    Reducer::new(median_impl::<T>)
}

fn first_impl<T>(first: &[T], second: &[T]) -> T
where
    T: Copy + Default,
{
    first.first().or_else(|| second.first()).copied().unwrap_or_default()
}

fn last_impl<T>(first: &[T], second: &[T]) -> T
where
    T: Copy + Default,
{
    second.last().or_else(|| first.last()).copied().unwrap_or_default()
}

fn min_impl<T>(first: &[T], second: &[T]) -> T
where
    T: Copy + Default + PartialOrd,
{
    let mut iter = first.iter().chain(second);
    let Some(mut min) = iter.next() else {
        return T::default();
    };
    for value in iter {
        if value < min {
            min = value;
        }
    }
    *min
}

fn max_impl<T>(first: &[T], second: &[T]) -> T
where
    T: Copy + Default + PartialOrd,
{
    let mut iter = first.iter().chain(second);
    let Some(mut max) = iter.next() else {
        return T::default();
    };
    for value in iter {
        if value > max {
            max = value;
        }
    }
    *max
}

fn median_impl<T>(first: &[T], second: &[T]) -> T
where
    T: Copy + Default + Ord,
{
    let count = first.len() + second.len();
    if count == 0 {
        return T::default();
    }

    let mut values = Vec::with_capacity(count);
    values.extend_from_slice(first);
    values.extend_from_slice(second);
    values.sort_unstable();
    values[(count - 1) / 2]
}

macro_rules! average_float {
    ($name:ident, $impl_name:ident, $t:ty) => {
        pub fn $name() -> Reducer<$t> { Reducer::new($impl_name) }

        fn $impl_name(first: &[$t], second: &[$t]) -> $t {
            let count = first.len() + second.len();
            if count == 0 {
                return <$t>::default();
            }

            let mut total = <$t>::default();
            for value in first.iter().chain(second) {
                total += *value;
            }
            total / count as $t
        }
    };
}

macro_rules! average_unsigned {
    ($name:ident, $impl_name:ident, $t:ty, $acc:ty) => {
        pub fn $name() -> Reducer<$t> { Reducer::new($impl_name) }

        fn $impl_name(first: &[$t], second: &[$t]) -> $t {
            let count = first.len() + second.len();
            if count == 0 {
                return <$t>::default();
            }

            let mut total: $acc = 0;
            for value in first.iter().chain(second) {
                total += *value as $acc;
            }
            (total / count as $acc) as $t
        }
    };
}

macro_rules! average_signed {
    ($name:ident, $impl_name:ident, $t:ty, $acc:ty) => {
        pub fn $name() -> Reducer<$t> { Reducer::new($impl_name) }

        fn $impl_name(first: &[$t], second: &[$t]) -> $t {
            let count = first.len() + second.len();
            if count == 0 {
                return <$t>::default();
            }

            let mut total: $acc = 0;
            for value in first.iter().chain(second) {
                total += *value as $acc;
            }
            (total / count as $acc) as $t
        }
    };
}

average_float!(average_f32, average_f32_impl, f32);
average_float!(average_f64, average_f64_impl, f64);
average_unsigned!(average_u8, average_u8_impl, u8, u64);
average_unsigned!(average_u16, average_u16_impl, u16, u64);
average_unsigned!(average_u32, average_u32_impl, u32, u128);
average_unsigned!(average_u64, average_u64_impl, u64, u128);
average_unsigned!(average_u128, average_u128_impl, u128, u128);
average_unsigned!(average_usize, average_usize_impl, usize, u128);
average_signed!(average_i8, average_i8_impl, i8, i64);
average_signed!(average_i16, average_i16_impl, i16, i64);
average_signed!(average_i32, average_i32_impl, i32, i128);
average_signed!(average_i64, average_i64_impl, i64, i128);
average_signed!(average_i128, average_i128_impl, i128, i128);
average_signed!(average_isize, average_isize_impl, isize, i128);

pub fn median_f32() -> Reducer<f32> { Reducer::new(median_f32_impl) }

pub fn median_f64() -> Reducer<f64> { Reducer::new(median_f64_impl) }

fn median_f32_impl(first: &[f32], second: &[f32]) -> f32 {
    let count = first.len() + second.len();
    let mut values = Vec::with_capacity(first.len() + second.len());
    values.extend(first.iter().chain(second).filter(|value| !value.is_nan()));
    if values.is_empty() {
        if count > 0 {
            return f32::NAN;
        }
        return f32::default();
    }

    values.sort_unstable_by(f32::total_cmp);
    values[(values.len() - 1) / 2]
}

fn median_f64_impl(first: &[f64], second: &[f64]) -> f64 {
    let count = first.len() + second.len();
    let mut values = Vec::with_capacity(first.len() + second.len());
    values.extend(first.iter().chain(second).filter(|value| !value.is_nan()));
    if values.is_empty() {
        if count > 0 {
            return f64::NAN;
        }
        return f64::default();
    }

    values.sort_unstable_by(f64::total_cmp);
    values[(values.len() - 1) / 2]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn average_handles_split_windows() {
        assert_eq!(average_f32().reduce(&[1.0, 2.0], &[3.0, 4.0]), 2.5);
        assert_eq!(average_u32().reduce(&[1, 2], &[3, 4]), 2);
        assert_eq!(average_u128().reduce(&[1, 2], &[3, 4]), 2);
        assert_eq!(average_i128().reduce(&[-2, -1], &[1, 2]), 0);
    }

    #[test]
    fn average_handles_integer_edge_values() {
        assert_eq!(average_u64().reduce(&[u64::MAX, u64::MAX - 2], &[]), u64::MAX - 1);
        assert_eq!(
            average_usize().reduce(&[usize::MAX, usize::MAX - 2], &[]),
            usize::MAX - 1
        );
        assert_eq!(
            average_u128().reduce(&[u128::MAX / 2, u128::MAX / 2], &[]),
            u128::MAX / 2
        );
        assert_eq!(average_i64().reduce(&[i64::MIN + 2, i64::MIN + 4], &[]), i64::MIN + 3);
        assert_eq!(average_i64().reduce(&[i64::MAX - 1, i64::MAX - 3], &[]), i64::MAX - 2);
    }

    #[test]
    fn min_max_handle_empty_windows() {
        assert_eq!(min::<i32>().reduce(&[], &[]), 0);
        assert_eq!(max::<i32>().reduce(&[1, 5], &[3]), 5);
        assert_eq!(min::<i32>().reduce(&[1, -5], &[3]), -5);
        assert_eq!(max::<i32>().reduce(&[1, -5], &[3]), 3);
    }

    #[test]
    fn median_handles_split_windows() {
        assert_eq!(median::<i32>().reduce(&[9, 1], &[5]), 5);
        assert_eq!(median::<i32>().reduce(&[9, 1], &[5, 3]), 3);
        assert_eq!(median::<i32>().reduce(&[], &[]), 0);
    }

    #[test]
    fn median_float_ignores_nan_values() {
        assert_eq!(median_f32().reduce(&[f32::NAN, 9.0, 1.0], &[5.0]), 5.0);
        assert!(median_f32().reduce(&[f32::NAN], &[f32::NAN]).is_nan());
        assert_eq!(median_f32().reduce(&[], &[]), 0.0);
        assert_eq!(median_f64().reduce(&[f64::NAN, 9.0, 1.0], &[5.0, 3.0]), 3.0);
        assert!(median_f64().reduce(&[f64::NAN], &[f64::NAN]).is_nan());
        assert_eq!(median_f64().reduce(&[], &[]), 0.0);
    }
}