pub struct Reducer<T> {
reduce: fn(&[T], &[T]) -> T,
}
impl<T> Clone for Reducer<T> {
fn clone(&self) -> Self { *self }
}
impl<T> Copy for Reducer<T> {}
impl<T> Reducer<T> {
pub const fn new(reduce: fn(&[T], &[T]) -> T) -> Self { Self { reduce } }
pub fn reduce(self, first: &[T], second: &[T]) -> T { (self.reduce)(first, second) }
}
pub mod reducers {
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 minimum<T>() -> Reducer<T>
where
T: Copy + Default + PartialOrd,
{
min()
}
pub fn max<T>() -> Reducer<T>
where
T: Copy + Default + PartialOrd,
{
Reducer::new(max_impl::<T>)
}
pub fn maximum<T>() -> Reducer<T>
where
T: Copy + Default + PartialOrd,
{
max()
}
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().copied() 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().copied() 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).copied().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).copied().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::reducers;
#[test]
fn average_handles_split_windows() {
assert_eq!(reducers::average_f32().reduce(&[1.0, 2.0], &[3.0, 4.0]), 2.5);
assert_eq!(reducers::average_u32().reduce(&[1, 2], &[3, 4]), 2);
assert_eq!(reducers::average_u128().reduce(&[1, 2], &[3, 4]), 2);
assert_eq!(reducers::average_i128().reduce(&[-2, -1], &[1, 2]), 0);
}
#[test]
fn average_handles_integer_edge_values() {
assert_eq!(
reducers::average_u64().reduce(&[u64::MAX, u64::MAX - 2], &[]),
u64::MAX - 1
);
assert_eq!(
reducers::average_usize().reduce(&[usize::MAX, usize::MAX - 2], &[]),
usize::MAX - 1
);
assert_eq!(
reducers::average_u128().reduce(&[u128::MAX / 2, u128::MAX / 2], &[]),
u128::MAX / 2
);
assert_eq!(
reducers::average_i64().reduce(&[i64::MIN + 2, i64::MIN + 4], &[]),
i64::MIN + 3
);
assert_eq!(
reducers::average_i64().reduce(&[i64::MAX - 1, i64::MAX - 3], &[]),
i64::MAX - 2
);
}
#[test]
fn min_max_handle_empty_windows() {
assert_eq!(reducers::min::<i32>().reduce(&[], &[]), 0);
assert_eq!(reducers::max::<i32>().reduce(&[1, 5], &[3]), 5);
assert_eq!(reducers::minimum::<i32>().reduce(&[1, -5], &[3]), -5);
assert_eq!(reducers::maximum::<i32>().reduce(&[1, -5], &[3]), 3);
}
#[test]
fn median_handles_split_windows() {
assert_eq!(reducers::median::<i32>().reduce(&[9, 1], &[5]), 5);
assert_eq!(reducers::median::<i32>().reduce(&[9, 1], &[5, 3]), 3);
assert_eq!(reducers::median::<i32>().reduce(&[], &[]), 0);
}
#[test]
fn median_float_ignores_nan_values() {
assert_eq!(reducers::median_f32().reduce(&[f32::NAN, 9.0, 1.0], &[5.0]), 5.0);
assert!(reducers::median_f32().reduce(&[f32::NAN], &[f32::NAN]).is_nan());
assert_eq!(reducers::median_f32().reduce(&[], &[]), 0.0);
assert_eq!(reducers::median_f64().reduce(&[f64::NAN, 9.0, 1.0], &[5.0, 3.0]), 3.0);
assert!(reducers::median_f64().reduce(&[f64::NAN], &[f64::NAN]).is_nan());
assert_eq!(reducers::median_f64().reduce(&[], &[]), 0.0);
}
}