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() || array.valid_count()? == 0 {
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 assert_eq!(
69 min.dtype(),
70 array.dtype(),
71 "MinMax min dtype mismatch {}",
72 array.encoding()
73 );
74
75 assert_eq!(
76 max.dtype(),
77 array.dtype(),
78 "MinMax max dtype mismatch {}",
79 array.encoding()
80 );
81
82 assert!(
83 min <= max,
84 "min > max: min={} max={} encoding={}",
85 min,
86 max,
87 array.encoding()
88 );
89
90 array
92 .statistics()
93 .set(Stat::Min, Precision::Exact(min.value().clone()));
94 array
95 .statistics()
96 .set(Stat::Max, Precision::Exact(max.value().clone()));
97 }
98
99 Ok(min_max)
100}
101
102#[cfg(test)]
103mod tests {
104 use arrow_buffer::BooleanBuffer;
105 use vortex_buffer::buffer;
106
107 use crate::arrays::{BoolArray, NullArray, PrimitiveArray};
108 use crate::compute::{MinMaxResult, min_max};
109 use crate::validity::Validity;
110
111 #[test]
112 fn test_prim_max() {
113 let p = PrimitiveArray::new(buffer![1, 2, 3], Validity::NonNullable);
114 assert_eq!(
115 min_max(&p).unwrap(),
116 Some(MinMaxResult {
117 min: 1.into(),
118 max: 3.into()
119 })
120 );
121 }
122
123 #[test]
124 fn test_bool_max() {
125 let p = BoolArray::new(
126 BooleanBuffer::from([true, true, true].as_slice()),
127 Validity::NonNullable,
128 );
129 assert_eq!(
130 min_max(&p).unwrap(),
131 Some(MinMaxResult {
132 min: true.into(),
133 max: true.into()
134 })
135 );
136
137 let p = BoolArray::new(
138 BooleanBuffer::from([false, false, false].as_slice()),
139 Validity::NonNullable,
140 );
141 assert_eq!(
142 min_max(&p).unwrap(),
143 Some(MinMaxResult {
144 min: false.into(),
145 max: false.into()
146 })
147 );
148
149 let p = BoolArray::new(
150 BooleanBuffer::from([false, true, false].as_slice()),
151 Validity::NonNullable,
152 );
153 assert_eq!(
154 min_max(&p).unwrap(),
155 Some(MinMaxResult {
156 min: false.into(),
157 max: true.into()
158 })
159 );
160 }
161
162 #[test]
163 fn test_null() {
164 let p = NullArray::new(1);
165 assert_eq!(min_max(&p).unwrap(), None);
166 }
167}