vortex_array/arrays/primitive/compute/
min_max.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use itertools::Itertools;
5use vortex_dtype::{DType, NativePType, match_each_native_ptype};
6use vortex_error::VortexResult;
7use vortex_mask::Mask;
8use vortex_scalar::{Scalar, ScalarValue};
9
10use crate::arrays::{PrimitiveArray, PrimitiveVTable};
11use crate::compute::{MinMaxKernel, MinMaxKernelAdapter, MinMaxResult};
12use crate::register_kernel;
13
14impl MinMaxKernel for PrimitiveVTable {
15    fn min_max(&self, array: &PrimitiveArray) -> VortexResult<Option<MinMaxResult>> {
16        match_each_native_ptype!(array.ptype(), |T| {
17            compute_min_max_with_validity::<T>(array)
18        })
19    }
20}
21
22register_kernel!(MinMaxKernelAdapter(PrimitiveVTable).lift());
23
24#[inline]
25fn compute_min_max_with_validity<T>(array: &PrimitiveArray) -> VortexResult<Option<MinMaxResult>>
26where
27    T: Into<ScalarValue> + NativePType,
28{
29    Ok(match array.validity_mask()? {
30        Mask::AllTrue(_) => compute_min_max(array.as_slice::<T>().iter(), array.dtype()),
31        Mask::AllFalse(_) => None,
32        Mask::Values(v) => compute_min_max(
33            array
34                .as_slice::<T>()
35                .iter()
36                .zip(v.boolean_buffer().iter())
37                .filter_map(|(v, m)| m.then_some(v)),
38            array.dtype(),
39        ),
40    })
41}
42
43fn compute_min_max<'a, T>(iter: impl Iterator<Item = &'a T>, dtype: &DType) -> Option<MinMaxResult>
44where
45    T: Into<ScalarValue> + NativePType,
46{
47    // `total_compare` function provides a total ordering (even for NaN values).
48    // However, we exclude NaNs from min max as they're not useful for any purpose where min/max would be used
49    match iter
50        .filter(|v| !v.is_nan())
51        .minmax_by(|a, b| a.total_compare(**b))
52    {
53        itertools::MinMaxResult::NoElements => None,
54        itertools::MinMaxResult::OneElement(&x) => {
55            let scalar = Scalar::new(dtype.clone(), x.into());
56            Some(MinMaxResult {
57                min: scalar.clone(),
58                max: scalar,
59            })
60        }
61        itertools::MinMaxResult::MinMax(&min, &max) => Some(MinMaxResult {
62            min: Scalar::new(dtype.clone(), min.into()),
63            max: Scalar::new(dtype.clone(), max.into()),
64        }),
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use vortex_buffer::buffer;
71
72    use crate::arrays::PrimitiveArray;
73    use crate::compute::min_max;
74    use crate::validity::Validity;
75
76    #[test]
77    fn min_max_nan() {
78        let array = PrimitiveArray::new(
79            buffer![f32::NAN, -f32::NAN, -1.0, 1.0],
80            Validity::NonNullable,
81        );
82        let min_max = min_max(array.as_ref()).unwrap().unwrap();
83        assert_eq!(f32::try_from(min_max.min).unwrap(), -1.0);
84        assert_eq!(f32::try_from(min_max.max).unwrap(), 1.0);
85    }
86
87    #[test]
88    fn min_max_inf() {
89        let array = PrimitiveArray::new(
90            buffer![f32::INFINITY, f32::NEG_INFINITY, -1.0, 1.0],
91            Validity::NonNullable,
92        );
93        let min_max = min_max(array.as_ref()).unwrap().unwrap();
94        assert_eq!(f32::try_from(min_max.min).unwrap(), f32::NEG_INFINITY);
95        assert_eq!(f32::try_from(min_max.max).unwrap(), f32::INFINITY);
96    }
97}