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