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