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(count != 0, array.dtype().nullability()),
59                        indices.len(),
60                    )
61                    .into_array(),
62                ));
63            }
64        }
65
66        let indices_values = indices.clone().execute::<PrimitiveArray>(ctx)?;
67        let buffer = match_each_integer_ptype!(indices_values.ptype(), |I| {
68            take_bits(
69                array.bit_buffer_view(),
70                indices_values.as_slice::<I>(),
71                mask.values().map(|values| values.bit_buffer().as_view()),
72            )
73        });
74
75        let validity = array.validity()?.take(indices)?;
76        Ok(Some(BoolArray::new(buffer, validity).into_array()))
77    }
78}
79
80fn take_contiguous_ranges(
81    array: ArrayView<'_, Bool>,
82    indices: ArrayView<'_, PiecewiseSequence>,
83    indices_ref: &ArrayRef,
84    ctx: &mut ExecutionCtx,
85) -> VortexResult<Option<ArrayRef>> {
86    let Some((starts, lengths)) = maybe_contiguous_slices(indices, ctx)? else {
87        return Ok(None);
88    };
89    let source = array.to_bit_buffer();
90    let output_len = indices_ref.len();
91    let buffer = match lengths {
92        Columnar::Constant(lengths) => {
93            let length = constant_unsigned_usize(&lengths);
94            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
95                take_bit_slices_constant_length(
96                    &source,
97                    starts.as_slice::<S>(),
98                    length,
99                    output_len,
100                )?
101            })
102        }
103        Columnar::Canonical(lengths) => {
104            let lengths = lengths.into_primitive();
105            match_each_unsigned_integer_ptype!(starts.ptype(), |S| {
106                match_each_unsigned_integer_ptype!(lengths.ptype(), |L| {
107                    take_bit_slices(
108                        &source,
109                        starts.as_slice::<S>(),
110                        lengths.as_slice::<L>(),
111                        output_len,
112                    )?
113                })
114            })
115        }
116    };
117
118    Ok(Some(
119        BoolArray::new(buffer, array.validity()?.take(indices_ref)?).into_array(),
120    ))
121}
122
123fn take_bit_slices_constant_length<S>(
124    source: &BitBuffer,
125    starts: &[S],
126    length: usize,
127    output_len: usize,
128) -> VortexResult<BitBuffer>
129where
130    S: UnsignedPType,
131{
132    let computed_len = starts
133        .len()
134        .checked_mul(length)
135        .ok_or_else(|| vortex_err!("PiecewiseSequenceArray output length overflows usize"))?;
136    vortex_ensure_eq!(
137        computed_len,
138        output_len,
139        "PiecewiseSequenceArray expanded length does not match declared length",
140    );
141
142    let mut values = BitBufferMut::with_capacity(output_len);
143    for start in starts {
144        let start = start.as_();
145        values.append_buffer(&source.slice(start..).slice(..length));
146    }
147
148    Ok(values.freeze())
149}
150
151fn take_bit_slices<S, L>(
152    source: &BitBuffer,
153    starts: &[S],
154    lengths: &[L],
155    output_len: usize,
156) -> VortexResult<BitBuffer>
157where
158    S: UnsignedPType,
159    L: UnsignedPType,
160{
161    let mut values = BitBufferMut::with_capacity(output_len);
162    for (&start, &length) in starts.iter().zip_eq(lengths) {
163        let start = start.as_();
164        let length = length.as_();
165        values.append_buffer(&source.slice(start..).slice(..length));
166    }
167
168    vortex_ensure_eq!(
169        values.len(),
170        output_len,
171        "PiecewiseSequenceArray expanded length does not match declared length",
172    );
173    Ok(values.freeze())
174}
175
176#[cfg(test)]
177mod test {
178    use rstest::rstest;
179    use vortex_buffer::buffer;
180
181    use crate::IntoArray as _;
182    use crate::VortexSessionExecute;
183    use crate::array_session;
184    use crate::arrays::BoolArray;
185    use crate::arrays::PrimitiveArray;
186    use crate::arrays::bool::BoolArrayExt;
187    use crate::assert_arrays_eq;
188    use crate::compute::conformance::take::test_take_conformance;
189    use crate::validity::Validity;
190
191    #[test]
192    fn take_nullable() {
193        let mut ctx = array_session().create_execution_ctx();
194        let reference = BoolArray::from_iter(vec![
195            Some(false),
196            Some(true),
197            Some(false),
198            None,
199            Some(false),
200        ]);
201
202        let b = reference
203            .take(buffer![0, 3, 4].into_array())
204            .unwrap()
205            .execute::<BoolArray>(&mut ctx)
206            .unwrap();
207        assert_eq!(
208            b.to_bit_buffer(),
209            BoolArray::from_iter([Some(false), None, Some(false)]).to_bit_buffer()
210        );
211
212        let all_invalid_indices = PrimitiveArray::from_option_iter([None::<i32>, None, None]);
213        let b = reference.take(all_invalid_indices.into_array()).unwrap();
214        assert_arrays_eq!(b, BoolArray::from_iter([None, None, None]), &mut ctx);
215    }
216
217    #[test]
218    fn test_bool_array_take_with_null_out_of_bounds_indices() {
219        let mut ctx = array_session().create_execution_ctx();
220        let values = BoolArray::from_iter(vec![Some(false), Some(true), None, None, Some(false)]);
221        let indices = PrimitiveArray::new(
222            buffer![0, 3, 100],
223            Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
224        );
225        let actual = values.take(indices.into_array()).unwrap();
226
227        // position 3 is null, the third index is null
228        assert_arrays_eq!(
229            actual,
230            BoolArray::from_iter([Some(false), None, None]),
231            &mut ctx
232        );
233    }
234
235    #[test]
236    fn test_non_null_bool_array_take_with_null_out_of_bounds_indices() {
237        let mut ctx = array_session().create_execution_ctx();
238        let values = BoolArray::from_iter(vec![false, true, false, true, false]);
239        let indices = PrimitiveArray::new(
240            buffer![0, 3, 100],
241            Validity::Array(BoolArray::from_iter([true, true, false]).into_array()),
242        );
243        let actual = values.take(indices.into_array()).unwrap();
244        // the third index is null
245        assert_arrays_eq!(
246            actual,
247            BoolArray::from_iter([Some(false), Some(true), None]),
248            &mut ctx
249        );
250    }
251
252    #[test]
253    fn test_bool_array_take_all_null_indices() {
254        let mut ctx = array_session().create_execution_ctx();
255        let values = BoolArray::from_iter(vec![Some(false), Some(true), None, None, Some(false)]);
256        let indices = PrimitiveArray::new(
257            buffer![0, 3, 100],
258            Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
259        );
260        let actual = values.take(indices.into_array()).unwrap();
261        assert_arrays_eq!(actual, BoolArray::from_iter([None, None, None]), &mut ctx);
262    }
263
264    #[test]
265    fn test_non_null_bool_array_take_all_null_indices() {
266        let mut ctx = array_session().create_execution_ctx();
267        let values = BoolArray::from_iter(vec![false, true, false, true, false]);
268        let indices = PrimitiveArray::new(
269            buffer![0, 3, 100],
270            Validity::Array(BoolArray::from_iter([false, false, false]).into_array()),
271        );
272        let actual = values.take(indices.into_array()).unwrap();
273        assert_arrays_eq!(actual, BoolArray::from_iter([None, None, None]), &mut ctx);
274    }
275
276    #[rstest]
277    #[case(BoolArray::from_iter([true, false, true, true, false]))]
278    #[case(BoolArray::from_iter([Some(true), None, Some(false), Some(true), None]))]
279    #[case(BoolArray::from_iter([true, false]))]
280    #[case(BoolArray::from_iter([true]))]
281    fn test_take_bool_conformance(#[case] array: BoolArray) {
282        test_take_conformance(
283            &array.into_array(),
284            &mut array_session().create_execution_ctx(),
285        );
286    }
287}