vortex_array/arrays/primitive/compute/
min_max.rs

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