vortex_array/arrays/dict/compute/
min_max.rs1use vortex_error::VortexResult;
5
6use super::DictArray;
7use super::DictVTable;
8use crate::Array as _;
9use crate::IntoArray;
10use crate::arrays::BoolArray;
11use crate::builtins::ArrayBuiltins;
12use crate::compute::MinMaxKernel;
13use crate::compute::MinMaxKernelAdapter;
14use crate::compute::MinMaxResult;
15use crate::compute::min_max;
16use crate::register_kernel;
17use crate::validity::Validity;
18
19impl MinMaxKernel for DictVTable {
20 fn min_max(&self, array: &DictArray) -> VortexResult<Option<MinMaxResult>> {
21 let codes_validity = array.codes().validity_mask()?;
22 if codes_validity.all_false() {
23 return Ok(None);
24 }
25
26 if array.has_all_values_referenced() {
28 return min_max(array.values());
29 }
30
31 let unreferenced_mask = BoolArray::new(
33 array.compute_referenced_values_mask(true)?,
34 Validity::NonNullable,
35 )
36 .into_array();
37
38 min_max(&array.values().clone().mask(unreferenced_mask)?)
39 }
40}
41
42register_kernel!(MinMaxKernelAdapter(DictVTable).lift());
43
44#[cfg(test)]
45mod tests {
46 use rstest::rstest;
47 use vortex_buffer::buffer;
48
49 use super::DictArray;
50 use crate::Array;
51 use crate::IntoArray;
52 use crate::arrays::PrimitiveArray;
53 use crate::builders::dict::dict_encode;
54 use crate::compute::min_max;
55
56 fn assert_min_max(array: &dyn Array, expected: Option<(i32, i32)>) {
57 match (min_max(array).unwrap(), expected) {
58 (Some(result), Some((expected_min, expected_max))) => {
59 assert_eq!(i32::try_from(&result.min).unwrap(), expected_min);
60 assert_eq!(i32::try_from(&result.max).unwrap(), expected_max);
61 }
62 (None, None) => {}
63 (got, expected) => panic!(
64 "min_max mismatch: expected {:?}, got {:?}",
65 expected,
66 got.as_ref().map(|r| (
67 i32::try_from(&r.min.clone()).ok(),
68 i32::try_from(&r.max.clone()).ok()
69 ))
70 ),
71 }
72 }
73
74 #[rstest]
75 #[case::covering(
76 DictArray::try_new(
77 buffer![0u32, 1, 2, 3, 0, 1].into_array(),
78 buffer![10i32, 20, 30, 40].into_array(),
79 ).unwrap(),
80 (10, 40)
81 )]
82 #[case::non_covering_duplicates(
83 DictArray::try_new(
84 buffer![1u32, 1, 1, 3, 3].into_array(),
85 buffer![1i32, 2, 3, 4, 5].into_array(),
86 ).unwrap(),
87 (2, 4)
88 )]
89 #[case::non_covering_gaps(
91 DictArray::try_new(
92 buffer![0u32, 2, 4].into_array(),
93 buffer![1i32, 2, 3, 4, 5].into_array(),
94 ).unwrap(),
95 (1, 5)
96 )]
97 #[case::single(dict_encode(&buffer![42i32].into_array()).unwrap(), (42, 42))]
98 #[case::nullable_codes(
99 DictArray::try_new(
100 PrimitiveArray::from_option_iter([Some(0u32), None, Some(1), Some(2)]).into_array(),
101 buffer![10i32, 20, 30].into_array(),
102 ).unwrap(),
103 (10, 30)
104 )]
105 #[case::nullable_values(
106 dict_encode(
107 PrimitiveArray::from_option_iter([Some(1i32), None, Some(2), Some(1), None]).as_ref()
108 ).unwrap(),
109 (1, 2)
110 )]
111 fn test_min_max(#[case] dict: DictArray, #[case] expected: (i32, i32)) {
112 assert_min_max(dict.as_ref(), Some(expected));
113 }
114
115 #[test]
116 fn test_sliced_dict() {
117 let reference = PrimitiveArray::from_iter([1, 5, 10, 50, 100]);
118 let dict = dict_encode(reference.as_ref()).unwrap();
119 let sliced = dict.slice(1..3).unwrap();
120 assert_min_max(sliced.as_ref(), Some((5, 10)));
121 }
122
123 #[rstest]
124 #[case::empty(
125 DictArray::try_new(
126 PrimitiveArray::from_iter(Vec::<u32>::new()).into_array(),
127 buffer![10i32, 20, 30].into_array(),
128 ).unwrap()
129 )]
130 #[case::all_null_codes(
131 DictArray::try_new(
132 PrimitiveArray::from_option_iter([Option::<u32>::None, None, None]).into_array(),
133 buffer![10i32, 20, 30].into_array(),
134 ).unwrap()
135 )]
136 fn test_min_max_none(#[case] dict: DictArray) {
137 assert_min_max(dict.as_ref(), None);
138 }
139}