Skip to main content

vortex_array/arrays/bool/compute/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use itertools::Itertools as _;
5use vortex_buffer::BitBuffer;
6use vortex_buffer::BitBufferMut;
7use vortex_buffer::take_bits;
8use vortex_error::VortexResult;
9use vortex_error::vortex_ensure_eq;
10use vortex_error::vortex_err;
11use vortex_mask::Mask;
12
13use crate::ArrayRef;
14use crate::Columnar;
15use crate::IntoArray;
16use crate::array::ArrayView;
17use crate::arrays::Bool;
18use crate::arrays::BoolArray;
19use crate::arrays::ConstantArray;
20use crate::arrays::PiecewiseSequence;
21use crate::arrays::PrimitiveArray;
22use crate::arrays::bool::BoolArrayExt;
23use crate::arrays::dict::TakeExecute;
24use crate::arrays::piecewise_sequence::constant_unsigned_usize;
25use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
26use crate::dtype::UnsignedPType;
27use crate::executor::ExecutionCtx;
28use crate::match_each_integer_ptype;
29use crate::match_each_unsigned_integer_ptype;
30use crate::scalar::Scalar;
31
32impl TakeExecute for Bool {
33    fn take(
34        array: ArrayView<'_, Bool>,
35        indices: &ArrayRef,
36        ctx: &mut ExecutionCtx,
37    ) -> VortexResult<Option<ArrayRef>> {
38        if let Some(piecewise_indices) = indices.as_opt::<PiecewiseSequence>()
39            && let Some(taken) = take_contiguous_ranges(array, piecewise_indices, indices, ctx)?
40        {
41            return Ok(Some(taken));
42        }
43
44        let mask = indices.validity()?.execute_mask(indices.len(), ctx)?;
45        if matches!(mask, Mask::AllFalse(_)) {
46            return Ok(Some(
47                ConstantArray::new(Scalar::null(array.dtype().as_nullable()), indices.len())
48                    .into_array(),
49            ));
50        }
51
52        if matches!(mask, Mask::AllTrue(_)) && !array.dtype().is_nullable() {
53            let source = array.bit_buffer_view();
54            let count = source.true_count();
55            if count == 0 || count == source.len() {
56                return Ok(Some(
57                    ConstantArray::new(
58                        Scalar::bool(
59                            count != 0,
60                            array.dtype().nullability() | indices.dtype().nullability(),
61                        ),
62                        indices.len(),
63                    )
64                    .into_array(),
65                ));
66            }
67        }
68
69        let indices_values = indices.clone().execute::<PrimitiveArray>(ctx)?;
70        let buffer = match_each_integer_ptype!(indices_values.ptype(), |I| {
71            take_bits(
72                array.bit_buffer_view(),
73                indices_values.as_slice::<I>(),
74                mask.values().map(|values| values.bit_buffer().as_view()),
75            )
76        });
77
78        let validity = array.validity()?.take(indices)?;
79        Ok(Some(BoolArray::new(buffer, validity).into_array()))
80    }
81}
82
83fn take_contiguous_ranges(
84    array: ArrayView<'_, Bool>,
85    indices: ArrayView<'_, PiecewiseSequence>,
86    indices_ref: &ArrayRef,
87    ctx: &mut ExecutionCtx,
88) -> VortexResult<Option<ArrayRef>> {
89    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
90        return Ok(None);
91    };
92    let source = array.to_bit_buffer();
93    let output_len = indices_ref.len();
94    let buffer = match lengths {
95        Columnar::Constant(lengths) => {
96            let length = constant_unsigned_usize(&lengths);
97            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
98                take_bit_slices_constant_length(
99                    &source,
100                    starts.as_slice::<S>(),
101                    length,
102                    output_len,
103                )?
104            })
105        }
106        Columnar::Canonical(lengths) => {
107            let lengths = lengths.into_primitive();
108            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
109                match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
110                    take_bit_slices(
111                        &source,
112                        starts.as_slice::<S>(),
113                        lengths.as_slice::<L>(),
114                        output_len,
115                    )?
116                })
117            })
118        }
119    };
120
121    Ok(Some(
122        BoolArray::new(buffer, array.validity()?.take(indices_ref)?).into_array(),
123    ))
124}
125
126fn take_bit_slices_constant_length<S>(
127    source: &BitBuffer,
128    starts: &[S],
129    length: usize,
130    output_len: usize,
131) -> VortexResult<BitBuffer>
132where
133    S: UnsignedPType,
134{
135    let computed_len = starts
136        .len()
137        .checked_mul(length)
138        .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
139    vortex_ensure_eq!(
140        computed_len,
141        output_len,
142        "PiecewiseSequenceArray expanded length does not match declared length",
143    );
144
145    let mut values = BitBufferMut::with_capacity(output_len);
146    for start in starts {
147        let start = start.as_();
148        values.append_buffer(&source.slice(start..).slice(..length));
149    }
150
151    Ok(values.freeze())
152}
153
154fn take_bit_slices<S, L>(
155    source: &BitBuffer,
156    starts: &[S],
157    lengths: &[L],
158    output_len: usize,
159) -> VortexResult<BitBuffer>
160where
161    S: UnsignedPType,
162    L: UnsignedPType,
163{
164    let mut values = BitBufferMut::with_capacity(output_len);
165    for (&start, &length) in starts.iter().zip_eq(lengths) {
166        let start = start.as_();
167        let length = length.as_();
168        values.append_buffer(&source.slice(start..).slice(..length));
169    }
170
171    vortex_ensure_eq!(
172        values.len(),
173        output_len,
174        "PiecewiseSequenceArray expanded length does not match declared length",
175    );
176    Ok(values.freeze())
177}
178
179#[cfg(test)]
180mod test {
181    use rstest::rstest;
182    use vortex_buffer::buffer;
183    use vortex_error::VortexResult;
184
185    use crate::IntoArray as _;
186    use crate::VortexSessionExecute;
187    use crate::array_session;
188    use crate::arrays::BoolArray;
189    use crate::arrays::DictArray;
190    use crate::arrays::PrimitiveArray;
191    use crate::assert_arrays_eq;
192    use crate::compute::conformance::take::test_take_conformance;
193    use crate::validity::Validity;
194
195    #[test]
196    fn take_nullable() {
197        let mut ctx = array_session().create_execution_ctx();
198        let reference = BoolArray::from_iter(vec![
199            Some(false),
200            Some(true),
201            Some(false),
202            None,
203            Some(false),
204        ]);
205
206        let b = reference.take(buffer![0, 3, 4].into_array()).unwrap();
207        assert_arrays_eq!(
208            b,
209            BoolArray::from_iter([Some(false), None, Some(false)]),
210            &mut ctx
211        );
212
213        let all_invalid_indices = PrimitiveArray::from_option_iter([None::<i32>, None, None]);
214        let b = reference.take(all_invalid_indices.into_array()).unwrap();
215        assert_arrays_eq!(b, BoolArray::from_iter([None, None, None]), &mut ctx);
216    }
217
218    #[test]
219    fn test_bool_array_take_with_null_out_of_bounds_indices() {
220        let mut ctx = array_session().create_execution_ctx();
221        let values = BoolArray::from_iter(vec![Some(false), Some(true), None, None, Some(false)]);
222        let indices = PrimitiveArray::new(
223            buffer![0, 3, 100],
224            Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
225        );
226        let actual = values.take(indices.into_array()).unwrap();
227
228        // position 3 is null, the third index is null
229        assert_arrays_eq!(
230            actual,
231            BoolArray::from_iter([Some(false), None, None]),
232            &mut ctx
233        );
234    }
235
236    #[test]
237    fn test_non_null_bool_array_take_with_null_out_of_bounds_indices() {
238        let mut ctx = array_session().create_execution_ctx();
239        let values = BoolArray::from_iter(vec![false, true, false, true, false]);
240        let indices = PrimitiveArray::new(
241            buffer![0, 3, 100],
242            Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
243        );
244        let actual = values.take(indices.into_array()).unwrap();
245        // the third index is null
246        assert_arrays_eq!(
247            actual,
248            BoolArray::from_iter([Some(false), Some(true), None]),
249            &mut ctx
250        );
251    }
252
253    #[test]
254    fn test_bool_array_take_all_null_indices() {
255        let mut ctx = array_session().create_execution_ctx();
256        let values = BoolArray::from_iter(vec![Some(false), Some(true), None, None, Some(false)]);
257        let indices = PrimitiveArray::new(
258            buffer![0, 3, 100],
259            Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
260        );
261        let actual = values.take(indices.into_array()).unwrap();
262        assert_arrays_eq!(actual, BoolArray::from_iter([None, None, None]), &mut ctx);
263    }
264
265    #[test]
266    fn test_non_null_bool_array_take_all_null_indices() {
267        let mut ctx = array_session().create_execution_ctx();
268        let values = BoolArray::from_iter(vec![false, true, false, true, false]);
269        let indices = PrimitiveArray::new(
270            buffer![0, 3, 100],
271            Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
272        );
273        let actual = values.take(indices.into_array()).unwrap();
274        assert_arrays_eq!(actual, BoolArray::from_iter([None, None, None]), &mut ctx);
275    }
276
277    #[rstest]
278    #[case(true)]
279    #[case(false)]
280    fn test_non_null_constant_bool_take_with_nullable_indices(
281        #[case] value: bool,
282    ) -> VortexResult<()> {
283        let mut ctx = array_session().create_execution_ctx();
284        let values = BoolArray::from_iter([value]);
285        let codes = PrimitiveArray::new(buffer![0i64, 0, 0], Validity::AllValid);
286
287        let actual = DictArray::try_new(codes.into_array(), values.into_array())?.into_array();
288
289        assert_arrays_eq!(
290            actual,
291            BoolArray::from_iter([Some(value), Some(value), Some(value)]),
292            &mut ctx
293        );
294        Ok(())
295    }
296
297    #[rstest]
298    #[case(BoolArray::from_iter([true, false, true, true, false]))]
299    #[case(BoolArray::from_iter([Some(true), None, Some(false), Some(true), None]))]
300    #[case(BoolArray::from_iter([true, false]))]
301    #[case(BoolArray::from_iter([true]))]
302    fn test_take_bool_conformance(#[case] array: BoolArray) {
303        test_take_conformance(
304            &array.into_array(),
305            &mut array_session().create_execution_ctx(),
306        );
307    }
308}