vortex_array/compute/
min_max.rs

1use vortex_error::{VortexExpect, VortexResult, vortex_bail};
2use vortex_scalar::Scalar;
3
4use crate::stats::{Precision, Stat, StatsProviderExt};
5use crate::{Array, Encoding};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct MinMaxResult {
9    pub min: Scalar,
10    pub max: Scalar,
11}
12
13/// Computes the min and max of an array, returning the (min, max) values
14/// If the array is empty or has only nulls, the result is `None`.
15pub trait MinMaxFn<A> {
16    fn min_max(&self, array: A) -> VortexResult<Option<MinMaxResult>>;
17}
18
19impl<E: Encoding> MinMaxFn<&dyn Array> for E
20where
21    E: for<'a> MinMaxFn<&'a E::Array>,
22{
23    fn min_max(&self, array: &dyn Array) -> VortexResult<Option<MinMaxResult>> {
24        let array_ref = array
25            .as_any()
26            .downcast_ref::<E::Array>()
27            .vortex_expect("Failed to downcast array");
28        MinMaxFn::min_max(self, array_ref)
29    }
30}
31
32/// Computes the min & max of an array, returning the (min, max) values
33/// The return values are (min, max) scalars, where None indicates that the value is non-existent
34/// (e.g. for an empty array)
35/// The return value dtype is the non-nullable version of the array dtype
36///
37/// This will update the stats set of this array (as a side effect).
38pub fn min_max(array: &dyn Array) -> VortexResult<Option<MinMaxResult>> {
39    if array.is_empty() {
40        return Ok(None);
41    }
42
43    let min = array
44        .statistics()
45        .get_scalar(Stat::Min, array.dtype())
46        .and_then(Precision::as_exact);
47    let max = array
48        .statistics()
49        .get_scalar(Stat::Max, array.dtype())
50        .and_then(Precision::as_exact);
51
52    if let Some((min, max)) = min.zip(max) {
53        return Ok(Some(MinMaxResult { min, max }));
54    }
55
56    let min_max = if let Some(fn_) = array.vtable().min_max_fn() {
57        fn_.min_max(array)?
58    } else {
59        let canonical = array.to_canonical()?;
60        if let Some(fn_) = canonical.as_ref().vtable().min_max_fn() {
61            fn_.min_max(canonical.as_ref())?
62        } else {
63            vortex_bail!(NotImplemented: "min_max", array.encoding());
64        }
65    };
66
67    if let Some(MinMaxResult { min, max }) = min_max.as_ref() {
68        debug_assert_eq!(
69            min.dtype(),
70            array.dtype(),
71            "MinMax min dtype mismatch {}",
72            array.encoding()
73        );
74
75        array
76            .statistics()
77            .set(Stat::Min, Precision::exact(min.clone().into_value()));
78
79        debug_assert_eq!(
80            max.dtype(),
81            array.dtype(),
82            "MinMax max dtype mismatch {}",
83            array.encoding()
84        );
85        array
86            .statistics()
87            .set(Stat::Max, Precision::exact(max.clone().into_value()));
88
89        debug_assert!(
90            min <= max,
91            "min > max: min={} max={} encoding={}",
92            min,
93            max,
94            array.encoding()
95        );
96
97        // Update the stats set with the computed min/max
98        array
99            .statistics()
100            .set(Stat::Min, Precision::Exact(min.value().clone()));
101        array
102            .statistics()
103            .set(Stat::Max, Precision::Exact(max.value().clone()));
104    }
105
106    Ok(min_max)
107}
108
109#[cfg(test)]
110mod tests {
111    use arrow_buffer::BooleanBuffer;
112    use vortex_buffer::buffer;
113
114    use crate::arrays::{BoolArray, NullArray, PrimitiveArray};
115    use crate::compute::{MinMaxResult, min_max};
116    use crate::validity::Validity;
117
118    #[test]
119    fn test_prim_max() {
120        let p = PrimitiveArray::new(buffer![1, 2, 3], Validity::NonNullable);
121        assert_eq!(
122            min_max(&p).unwrap(),
123            Some(MinMaxResult {
124                min: 1.into(),
125                max: 3.into()
126            })
127        );
128    }
129
130    #[test]
131    fn test_bool_max() {
132        let p = BoolArray::new(
133            BooleanBuffer::from([true, true, true].as_slice()),
134            Validity::NonNullable,
135        );
136        assert_eq!(
137            min_max(&p).unwrap(),
138            Some(MinMaxResult {
139                min: true.into(),
140                max: true.into()
141            })
142        );
143
144        let p = BoolArray::new(
145            BooleanBuffer::from([false, false, false].as_slice()),
146            Validity::NonNullable,
147        );
148        assert_eq!(
149            min_max(&p).unwrap(),
150            Some(MinMaxResult {
151                min: false.into(),
152                max: false.into()
153            })
154        );
155
156        let p = BoolArray::new(
157            BooleanBuffer::from([false, true, false].as_slice()),
158            Validity::NonNullable,
159        );
160        assert_eq!(
161            min_max(&p).unwrap(),
162            Some(MinMaxResult {
163                min: false.into(),
164                max: true.into()
165            })
166        );
167    }
168
169    #[test]
170    fn test_null() {
171        let p = NullArray::new(1);
172        assert_eq!(min_max(&p).unwrap(), None);
173    }
174}