1use vortex_error::{VortexExpect, VortexResult, vortex_bail};
2
3use crate::arrays::{ConstantArray, NullArray};
4use crate::stats::{Precision, Stat};
5use crate::{Array, ArrayExt, Encoding};
6
7pub trait IsSortedFn<A> {
8 fn is_sorted(&self, array: A) -> VortexResult<bool>;
14
15 fn is_strict_sorted(&self, array: A) -> VortexResult<bool>;
16}
17
18impl<E: Encoding> IsSortedFn<&dyn Array> for E
19where
20 E: for<'a> IsSortedFn<&'a E::Array>,
21{
22 fn is_sorted(&self, array: &dyn Array) -> VortexResult<bool> {
23 let array_ref = array
24 .as_any()
25 .downcast_ref::<E::Array>()
26 .vortex_expect("Failed to downcast array");
27 IsSortedFn::is_sorted(self, array_ref)
28 }
29
30 fn is_strict_sorted(&self, array: &dyn Array) -> VortexResult<bool> {
31 let array_ref = array
32 .as_any()
33 .downcast_ref::<E::Array>()
34 .vortex_expect("Failed to downcast array");
35 IsSortedFn::is_strict_sorted(self, array_ref)
36 }
37}
38
39#[allow(clippy::wrong_self_convention)]
40pub trait IsSortedIteratorExt: Iterator
42where
43 <Self as Iterator>::Item: PartialOrd,
44{
45 fn is_strict_sorted(self) -> bool
46 where
47 Self: Sized,
48 Self::Item: PartialOrd,
49 {
50 self.is_sorted_by(|a, b| a < b)
51 }
52}
53
54impl<T> IsSortedIteratorExt for T
55where
56 T: Iterator + ?Sized,
57 T::Item: PartialOrd,
58{
59}
60
61pub fn is_sorted(array: &dyn Array) -> VortexResult<bool> {
62 if array.dtype().is_struct() {
64 return Ok(false);
65 }
66
67 if let Some(Precision::Exact(value)) = array.statistics().get_as::<bool>(Stat::IsSorted) {
68 return Ok(value);
69 }
70
71 let is_sorted = is_sorted_impl(array, false)?;
72 let array_stats = array.statistics();
73
74 if is_sorted {
75 array_stats.set(Stat::IsSorted, Precision::Exact(true.into()));
76 } else {
77 array_stats.set(Stat::IsSorted, Precision::Exact(false.into()));
78 array_stats.set(Stat::IsStrictSorted, Precision::Exact(false.into()));
79 }
80
81 Ok(is_sorted)
82}
83pub fn is_strict_sorted(array: &dyn Array) -> VortexResult<bool> {
84 if array.dtype().is_struct() {
86 return Ok(false);
87 }
88
89 if let Some(Precision::Exact(value)) = array.statistics().get_as::<bool>(Stat::IsStrictSorted) {
90 return Ok(value);
91 }
92
93 let is_sorted = is_sorted_impl(array, true)?;
94 let array_stats = array.statistics();
95
96 if is_sorted {
97 array_stats.set(Stat::IsSorted, Precision::Exact(true.into()));
98 array_stats.set(Stat::IsStrictSorted, Precision::Exact(true.into()));
99 } else {
100 array_stats.set(Stat::IsStrictSorted, Precision::Exact(false.into()));
101 }
102
103 Ok(is_sorted)
104}
105
106fn is_sorted_impl(array: &dyn Array, strict: bool) -> VortexResult<bool> {
107 if array.len() <= 1 {
109 return Ok(true);
110 }
111
112 if array.is::<ConstantArray>() || array.is::<NullArray>() {
114 return Ok(!strict);
115 }
116
117 let invalid_count = array.invalid_count()?;
118
119 if strict {
121 match invalid_count {
122 0 => {}
124 1 => {
126 if !array.is_invalid(0)? {
127 return Ok(false);
128 }
129 }
130 _ => return Ok(false),
131 }
132 }
133
134 let is_sorted = if let Some(vtable_fn) = array.vtable().is_sorted_fn() {
135 if strict {
136 vtable_fn.is_strict_sorted(array)?
137 } else {
138 vtable_fn.is_sorted(array)?
139 }
140 } else {
141 log::debug!("No is_sorted implementation found for {}", array.encoding());
142 let array = array.to_canonical()?;
143
144 if let Some(vtable_fn) = array.as_ref().vtable().is_sorted_fn() {
145 let array = array.as_ref();
146 if strict {
147 vtable_fn.is_strict_sorted(array)?
148 } else {
149 vtable_fn.is_sorted(array)?
150 }
151 } else {
152 vortex_bail!(
153 "No is_sorted function for canonical array: {}",
154 array.as_ref().encoding(),
155 )
156 }
157 };
158
159 Ok(is_sorted)
160}
161
162#[cfg(test)]
163mod tests {
164 use vortex_buffer::buffer;
165
166 use crate::Array;
167 use crate::arrays::{BoolArray, PrimitiveArray};
168 use crate::compute::{is_sorted, is_strict_sorted};
169 use crate::validity::Validity;
170
171 #[test]
172 fn test_is_sorted() {
173 assert!(
174 is_sorted(&PrimitiveArray::new(
175 buffer!(0, 1, 2, 3),
176 Validity::AllValid
177 ))
178 .unwrap()
179 );
180 assert!(
181 is_sorted(&PrimitiveArray::new(
182 buffer!(0, 1, 2, 3),
183 Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array())
184 ))
185 .unwrap()
186 );
187 assert!(
188 is_sorted(&PrimitiveArray::new(
189 buffer!(0, 1, 2, 3),
190 Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array())
191 ))
192 .unwrap()
193 );
194
195 assert!(
196 !is_sorted(&PrimitiveArray::new(
197 buffer!(0, 1, 3, 2),
198 Validity::AllValid
199 ))
200 .unwrap()
201 );
202 assert!(
203 !is_sorted(&PrimitiveArray::new(
204 buffer!(0, 1, 3, 2),
205 Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array()),
206 ))
207 .unwrap(),
208 );
209 }
210
211 #[test]
212 fn test_is_strict_sorted() {
213 assert!(
214 is_strict_sorted(&PrimitiveArray::new(
215 buffer!(0, 1, 2, 3),
216 Validity::AllValid
217 ))
218 .unwrap()
219 );
220 assert!(
221 is_strict_sorted(&PrimitiveArray::new(
222 buffer!(0, 1, 2, 3),
223 Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array())
224 ))
225 .unwrap()
226 );
227 assert!(
228 !is_strict_sorted(&PrimitiveArray::new(
229 buffer!(0, 1, 2, 3),
230 Validity::Array(BoolArray::from_iter([true, false, true, true]).into_array()),
231 ))
232 .unwrap(),
233 );
234
235 assert!(
236 !is_strict_sorted(&PrimitiveArray::new(
237 buffer!(0, 1, 3, 2),
238 Validity::Array(BoolArray::from_iter([false, true, true, true]).into_array()),
239 ))
240 .unwrap(),
241 );
242 }
243}