iterstats 0.7.0

Statistics for rust iterators.
Documentation
//! Normalize a dataset to a range.

use std::fmt::Debug;
use std::ops::{Add, Div, Mul, Sub};

use crate::Iterstats;

/// Normalize each item of an iterator.
pub trait Normalize<A = Self>: Sized {
    /// The type of the output.
    type Output;

    /// Calculate the z-score of each item.
    fn normalize<I>(iter: I, min: Self::Output, max: Self::Output) -> NormalizeIter<Self::Output>
    where
        I: Iterator<Item = A> + Clone;
}

/// Iterator for normalizing each item of the iterator.
#[derive(Debug, Clone)]
pub struct NormalizeIter<T> {
    iter: std::vec::IntoIter<T>,
    norm_min: T,
    norm_range: T,
    data_min: T,
    data_range: T,
}

impl<T> Iterator for NormalizeIter<T>
where
    T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + Debug,
{
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter
            .next()
            .map(|v| (v - self.data_min) / self.data_range * self.norm_range + self.norm_min)
    }
}

macro_rules! norm_impl {
    ($typ:ty) => {
        impl Normalize for $typ {
            type Output = $typ;

            fn normalize<I>(iter: I, min: $typ, max: $typ) -> NormalizeIter<Self::Output>
            where
                I: Iterator<Item = Self> + Clone,
            {
                let norm_range = max - min;
                let (data_min, data_max) = iter.clone().range().unwrap_or_default();
                let data_conv = iter.collect::<Vec<_>>().into_iter();
                let data_range = data_max - data_min;
                NormalizeIter {
                    iter: data_conv,
                    norm_min: min,
                    norm_range,
                    data_min,
                    data_range,
                }
            }
        }

        impl Normalize for &$typ {
            type Output = $typ;

            fn normalize<I>(iter: I, min: $typ, max: $typ) -> NormalizeIter<Self::Output>
            where
                I: Iterator<Item = Self> + Clone,
            {
                iter.map(|v| *v).normalize(min, max)
            }
        }
    };
}

norm_impl!(f64);
norm_impl!(f32);

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

    macro_rules! test_norm {
        ($name:ident: $typ:ty as $iter:expr ; iter => ($min:expr, $max:expr) $expected:expr ) => {
            paste! {
                #[test]
                fn [<$name:snake _iter >]() {
                    let zscores: Vec<_> = <&$typ>::normalize($iter.iter(), $min, $max).collect();
                    assert_eq!(zscores, $expected);
                }
            }
        };
        ($name:ident: $typ:ty as $iter:expr ; into_iter => ($min:expr, $max:expr) $expected:expr ) => {
            paste! {
                #[test]
                fn [<$name:snake _into_iter >]() {
                    let zscores: Vec<_> = <$typ>::normalize($iter.into_iter(), $min, $max).collect();
                    assert_eq!(zscores, $expected);
                }
            }
        };
        ($name:ident: $typ:ty as $iter:expr => ($min:expr, $max:expr) $expected:expr ) => {
            test_norm!($name: $typ as $iter; iter => ($min, $max) $expected);
            test_norm!($name: $typ as $iter; into_iter => ($min, $max) $expected);
        };
    }

    test_norm!(f64_0_100: f64 as [-1., -2., 3., 8.] => (0., 100.) vec![10., 0., 50., 100.]);
    test_norm!(f64_m10_90: f64 as [-1., -2., 3., 8.] => (-10., 90.) vec![0., -10., 40., 90.]);
    test_norm!(f32_0_1: f32 as [-1., -2., 3., 8.] => (0., 1.) vec![0.1, 0., 0.5, 1.]);
    test_norm!(f64_empty: f64 as [] => (0., 1.) vec![]);
}