vortex_array/compute/
min_max.rs1use 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
13pub 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
32pub 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 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}