vortex_array/arrays/primitive/compute/
zip.rs1use std::mem::MaybeUninit;
5
6use vortex_buffer::BufferMut;
7use vortex_error::VortexExpect;
8use vortex_error::VortexResult;
9use vortex_error::vortex_bail;
10use vortex_mask::Mask;
11
12use crate::ArrayRef;
13use crate::ExecutionCtx;
14use crate::IntoArray;
15use crate::array::ArrayView;
16use crate::arrays::Primitive;
17use crate::arrays::PrimitiveArray;
18use crate::builtins::ArrayBuiltins;
19use crate::dtype::NativePType;
20use crate::match_each_native_ptype;
21use crate::scalar_fn::fns::zip::ZipKernel;
22use crate::scalar_fn::fns::zip::zip_validity;
23
24impl ZipKernel for Primitive {
31 fn zip(
32 if_true: ArrayView<'_, Primitive>,
33 if_false: &ArrayRef,
34 mask: &ArrayRef,
35 ctx: &mut ExecutionCtx,
36 ) -> VortexResult<Option<ArrayRef>> {
37 let Some(if_false) = if_false.as_opt::<Primitive>() else {
38 return Ok(None);
39 };
40
41 if if_true.ptype() != if_false.ptype() {
42 vortex_bail!(
43 "zip requires if_true and if_false to share a primitive type, got {} and {}",
44 if_true.ptype(),
45 if_false.ptype()
46 );
47 }
48
49 let mask = mask.clone().fill_null(false)?.execute::<Mask>(ctx)?;
51 match &mask {
52 Mask::AllTrue(_) | Mask::AllFalse(_) => return Ok(None),
54 Mask::Values(_) => {}
55 }
56
57 let validity = zip_validity(if_true.validity()?, if_false.validity()?, &mask)?;
58
59 let array = match_each_native_ptype!(if_true.ptype(), |T| {
65 let values =
66 select_values::<T>(if_true.as_slice::<T>(), if_false.as_slice::<T>(), &mask);
67 PrimitiveArray::new(values.freeze(), validity).into_array()
68 });
69 Ok(Some(array))
70 }
71}
72
73fn select_values<T: NativePType>(
75 true_values: &[T],
76 false_values: &[T],
77 mask: &Mask,
78) -> BufferMut<T> {
79 let len = true_values.len();
80 let mut out = BufferMut::<T>::with_capacity(len);
81 {
82 let out_slice = out.spare_capacity_mut();
83
84 let mask_bits = mask
85 .values()
86 .vortex_expect("mask is Mask::Values")
87 .bit_buffer();
88 let chunks = mask_bits.chunks();
91
92 let mut base = 0;
93 for word in chunks.iter() {
94 let end = base + 64;
95 select_block(
96 word,
97 &true_values[base..end],
98 &false_values[base..end],
99 &mut out_slice[base..end],
100 );
101 base = end;
102 }
103
104 let remainder = chunks.remainder_len();
105 if remainder > 0 {
106 let end = base + remainder;
107 select_block(
108 chunks.remainder_bits(),
109 &true_values[base..end],
110 &false_values[base..end],
111 &mut out_slice[base..end],
112 );
113 }
114 }
115
116 unsafe { out.set_len(len) };
118 out
119}
120
121#[inline]
125fn select_block<T: NativePType>(
126 word: u64,
127 true_values: &[T],
128 false_values: &[T],
129 out: &mut [MaybeUninit<T>],
130) {
131 let n = out.len();
132 let true_values = &true_values[..n];
133 let false_values = &false_values[..n];
134 for j in 0..n {
135 let pick = (word >> j) & 1 == 1;
136 out[j].write(if pick {
137 true_values[j]
138 } else {
139 false_values[j]
140 });
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 #![allow(
147 clippy::cast_possible_truncation,
148 reason = "test fixtures use small indices that fit the target widths"
149 )]
150
151 use vortex_error::VortexResult;
152 use vortex_mask::Mask;
153
154 use crate::ArrayRef;
155 use crate::IntoArray;
156 use crate::VortexSessionExecute;
157 use crate::array_session;
158 use crate::arrays::Primitive;
159 use crate::arrays::PrimitiveArray;
160 use crate::assert_arrays_eq;
161 use crate::builtins::ArrayBuiltins;
162
163 #[test]
166 fn zip_nonnull_spans_mask_chunks() -> VortexResult<()> {
167 let len = 150usize;
168 let if_true = PrimitiveArray::from_iter(0..len as i64).into_array();
169 let if_false = PrimitiveArray::from_iter((0..len as i64).map(|i| 1_000 + i)).into_array();
170
171 let bits: Vec<bool> = (0..len).map(|i| i.is_multiple_of(3) || i == 64).collect();
172 let mask = Mask::from_iter(bits.iter().copied());
173
174 let mut ctx = array_session().create_execution_ctx();
175 let result = mask
176 .into_array()
177 .zip(if_true, if_false)?
178 .execute::<ArrayRef>(&mut ctx)?;
179 assert!(result.is::<Primitive>());
180
181 let expected = PrimitiveArray::from_iter(
182 (0..len).map(|i| if bits[i] { i as i64 } else { 1_000 + i as i64 }),
183 )
184 .into_array();
185 assert_arrays_eq!(result, expected, &mut ctx);
186 Ok(())
187 }
188
189 #[test]
192 fn zip_nullable_selects_values_and_validity() -> VortexResult<()> {
193 let len = 130usize;
194 let if_true =
195 PrimitiveArray::from_option_iter((0..len as i64).map(|i| (i % 4 != 0).then_some(i)))
196 .into_array();
197 let if_false = PrimitiveArray::from_option_iter(
198 (0..len as i64).map(|i| (i % 5 != 0).then_some(1_000 + i)),
199 )
200 .into_array();
201
202 let bits: Vec<bool> = (0..len).map(|i| i.is_multiple_of(2)).collect();
203 let mask = Mask::from_iter(bits.iter().copied());
204
205 let mut ctx = array_session().create_execution_ctx();
206 let result = mask
207 .into_array()
208 .zip(if_true, if_false)?
209 .execute::<ArrayRef>(&mut ctx)?;
210 assert!(result.is::<Primitive>());
211
212 let expected = PrimitiveArray::from_option_iter((0..len).map(|i| {
213 let v = i as i64;
214 if bits[i] {
215 (!i.is_multiple_of(4)).then_some(v)
216 } else {
217 (!i.is_multiple_of(5)).then_some(1_000 + v)
218 }
219 }))
220 .into_array();
221 assert_arrays_eq!(result, expected, &mut ctx);
222 Ok(())
223 }
224}