Skip to main content

vortex_runend/compute/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use num_traits::AsPrimitive;
5use num_traits::NumCast;
6use vortex_array::ArrayRef;
7use vortex_array::ArrayView;
8use vortex_array::ExecutionCtx;
9use vortex_array::IntoArray;
10use vortex_array::arrays::ConstantArray;
11use vortex_array::arrays::PrimitiveArray;
12use vortex_array::arrays::dict::TakeExecute;
13use vortex_array::dtype::UnsignedPType;
14use vortex_array::match_each_integer_ptype;
15use vortex_array::match_each_unsigned_integer_ptype;
16use vortex_array::scalar::Scalar;
17use vortex_array::validity::Validity;
18use vortex_buffer::Buffer;
19use vortex_buffer::BufferMut;
20use vortex_error::VortexResult;
21use vortex_error::vortex_bail;
22use vortex_mask::AllOr;
23use vortex_mask::Mask;
24
25use crate::RunEnd;
26use crate::array::RunEndArrayExt;
27use crate::array::RunEndArraySlotsExt;
28use crate::iter::trimmed_ends_iter;
29
30const SORTED_LINEAR_RUNS_PER_INDEX_THRESHOLD: usize = 16;
31const UNSORTED_LINEAR_RUNS_PER_INDEX_THRESHOLD: usize = 4;
32/// Sorting the indices and merging only beats per-index binary search once the run ends are too
33/// large to stay cache-resident; below this run count binary search wins.
34const UNSORTED_LINEAR_MIN_RUNS: usize = 1 << 19;
35/// Use a dense logical-position-to-run-index table when the array length is at most this many
36/// times the number of valid indices: building the table is O(array_len) and each index then
37/// resolves with a single unconditional gather.
38const TABLE_LEN_PER_INDEX_THRESHOLD: usize = 8;
39
40impl TakeExecute for RunEnd {
41    fn take(
42        array: ArrayView<'_, Self>,
43        indices: &ArrayRef,
44        ctx: &mut ExecutionCtx,
45    ) -> VortexResult<Option<ArrayRef>> {
46        let primitive_indices = indices.clone().execute::<PrimitiveArray>(ctx)?;
47        let indices_validity = primitive_indices.validity()?;
48        let indices_mask = indices_validity.execute_mask(primitive_indices.len(), ctx)?;
49
50        let taken = match_each_integer_ptype!(primitive_indices.ptype(), |P| {
51            take_indices(
52                array,
53                primitive_indices.as_slice::<P>(),
54                &indices_validity,
55                &indices_mask,
56                true,
57                ctx,
58            )?
59        });
60
61        Ok(Some(taken))
62    }
63}
64
65/// Perform a take operation on a RunEndArray without bounds-checking the indices.
66///
67/// The caller must guarantee that all valid indices are in bounds for the array.
68pub fn take_indices_unchecked<T: AsPrimitive<usize>>(
69    array: ArrayView<'_, RunEnd>,
70    indices: &[T],
71    validity: &Validity,
72    ctx: &mut ExecutionCtx,
73) -> VortexResult<ArrayRef> {
74    let validity_mask = validity.execute_mask(indices.len(), ctx)?;
75    take_indices(array, indices, validity, &validity_mask, false, ctx)
76}
77
78fn take_indices<T: AsPrimitive<usize>>(
79    array: ArrayView<'_, RunEnd>,
80    indices: &[T],
81    validity: &Validity,
82    validity_mask: &Mask,
83    check_bounds: bool,
84    ctx: &mut ExecutionCtx,
85) -> VortexResult<ArrayRef> {
86    if validity_mask.all_false() {
87        return Ok(
88            ConstantArray::new(Scalar::null(array.dtype().as_nullable()), indices.len())
89                .into_array(),
90        );
91    }
92
93    let stats = valid_indices_stats(indices, validity_mask, array.len(), check_bounds)?;
94    let ends = array.ends().clone().execute::<PrimitiveArray>(ctx)?;
95
96    let physical_indices = match_each_unsigned_integer_ptype!(ends.ptype(), |I| {
97        let ends = ends.as_slice::<I>();
98        // Run indices fit in u32 for any realistic array; the narrower physical indices halve
99        // the memory traffic of the downstream take on the values.
100        if ends.len() <= u32::MAX as usize {
101            PrimitiveArray::new(
102                physical_indices_with_stats::<_, _, u32>(
103                    ends,
104                    array.offset(),
105                    array.len(),
106                    indices,
107                    validity_mask,
108                    stats,
109                ),
110                validity.clone(),
111            )
112        } else {
113            PrimitiveArray::new(
114                physical_indices_with_stats::<_, _, u64>(
115                    ends,
116                    array.offset(),
117                    array.len(),
118                    indices,
119                    validity_mask,
120                    stats,
121                ),
122                validity.clone(),
123            )
124        }
125    });
126
127    array.values().take(physical_indices.into_array())
128}
129
130#[derive(Clone, Copy)]
131struct ValidIndicesStats {
132    count: usize,
133    sorted: bool,
134}
135
136fn physical_indices_with_stats<I, T, O>(
137    ends: &[I],
138    offset: usize,
139    array_len: usize,
140    indices: &[T],
141    validity_mask: &Mask,
142    stats: ValidIndicesStats,
143) -> Buffer<O>
144where
145    I: UnsignedPType,
146    T: AsPrimitive<usize>,
147    O: UnsignedPType,
148    usize: AsPrimitive<O>,
149{
150    if stats.count == 0 {
151        return Buffer::zeroed(indices.len());
152    }
153
154    if stats.sorted
155        && prefer_linear_scan(
156            ends.len(),
157            stats.count,
158            SORTED_LINEAR_RUNS_PER_INDEX_THRESHOLD,
159        )
160    {
161        return physical_indices_linear_sorted(ends, offset, indices, validity_mask);
162    }
163
164    // A dense take resolves fastest through the position table regardless of index ordering.
165    // Sorted indices reach here only when there are too many runs for the sorted linear scan,
166    // for example a narrow slice of a heavily run-encoded array where runs far exceed array_len.
167    if array_len <= stats.count.saturating_mul(TABLE_LEN_PER_INDEX_THRESHOLD) {
168        return physical_indices_table(ends, offset, array_len, indices, validity_mask);
169    }
170
171    if ends.len() >= UNSORTED_LINEAR_MIN_RUNS
172        && prefer_linear_scan(
173            ends.len(),
174            stats.count,
175            UNSORTED_LINEAR_RUNS_PER_INDEX_THRESHOLD,
176        )
177    {
178        return physical_indices_linear_unsorted(ends, offset, indices, validity_mask, stats.count);
179    }
180
181    physical_indices_binary(ends, offset, indices, validity_mask)
182}
183
184/// Count the valid indices and determine whether they are sorted, bounds-checking each valid
185/// index against `array_len` when `check_bounds` is set.
186fn valid_indices_stats<T: AsPrimitive<usize>>(
187    indices: &[T],
188    validity_mask: &Mask,
189    array_len: usize,
190    check_bounds: bool,
191) -> VortexResult<ValidIndicesStats> {
192    debug_assert_eq!(indices.len(), validity_mask.len());
193
194    let count = validity_mask.true_count();
195    if count == 0 {
196        return Ok(ValidIndicesStats {
197            count,
198            sorted: true,
199        });
200    }
201
202    let sorted = match validity_mask.bit_buffer() {
203        AllOr::All => valid_indices_sorted_all(indices, array_len, check_bounds)?,
204        AllOr::None => true,
205        AllOr::Some(validity) => {
206            valid_indices_sorted_masked(indices, validity.iter(), array_len, check_bounds)?
207        }
208    };
209
210    Ok(ValidIndicesStats { count, sorted })
211}
212
213fn valid_indices_sorted_all<T: AsPrimitive<usize>>(
214    indices: &[T],
215    array_len: usize,
216    check_bounds: bool,
217) -> VortexResult<bool> {
218    // Seed the comparison with the first index; an empty or single-element slice is trivially
219    // sorted, so the loop below starts from the second element.
220    let Some((first, rest)) = indices.split_first() else {
221        return Ok(true);
222    };
223
224    let mut previous_idx = first.as_();
225    if check_bounds {
226        check_index(previous_idx, array_len)?;
227    }
228
229    let mut sorted = true;
230    for idx in rest {
231        let idx = idx.as_();
232        if check_bounds {
233            check_index(idx, array_len)?;
234        }
235        if previous_idx > idx {
236            sorted = false;
237            if !check_bounds {
238                break;
239            }
240        }
241        previous_idx = idx;
242    }
243
244    Ok(sorted)
245}
246
247fn valid_indices_sorted_masked<T: AsPrimitive<usize>>(
248    indices: &[T],
249    is_valid: impl Iterator<Item = bool>,
250    array_len: usize,
251    check_bounds: bool,
252) -> VortexResult<bool> {
253    // Invalid positions are skipped without a bounds check, matching the take path that never
254    // dereferences them.
255    let mut valid = is_valid
256        .zip(indices.iter())
257        .filter(|(is_valid, _)| *is_valid)
258        .map(|(_, idx)| idx.as_());
259
260    // Seed the comparison with the first valid index; zero or one valid index is trivially
261    // sorted, so the loop below starts from the second valid index.
262    let Some(mut previous_idx) = valid.next() else {
263        return Ok(true);
264    };
265    if check_bounds {
266        check_index(previous_idx, array_len)?;
267    }
268
269    let mut sorted = true;
270    for idx in valid {
271        if check_bounds {
272            check_index(idx, array_len)?;
273        }
274        if previous_idx > idx {
275            sorted = false;
276            if !check_bounds {
277                break;
278            }
279        }
280        previous_idx = idx;
281    }
282
283    Ok(sorted)
284}
285
286fn prefer_linear_scan(
287    ends_len: usize,
288    valid_count: usize,
289    runs_per_index_threshold: usize,
290) -> bool {
291    ends_len <= valid_count.saturating_mul(runs_per_index_threshold)
292}
293
294fn check_index(index: usize, array_len: usize) -> VortexResult<()> {
295    if index >= array_len {
296        vortex_bail!(OutOfBounds: index, 0, array_len);
297    }
298    Ok(())
299}
300
301fn physical_indices_linear_sorted<I, T, O>(
302    ends: &[I],
303    offset: usize,
304    indices: &[T],
305    validity_mask: &Mask,
306) -> Buffer<O>
307where
308    I: UnsignedPType,
309    T: AsPrimitive<usize>,
310    O: UnsignedPType,
311    usize: AsPrimitive<O>,
312{
313    let mut run_idx = 0;
314
315    match validity_mask.bit_buffer() {
316        AllOr::All => Buffer::from_trusted_len_iter(indices.iter().map(|idx| {
317            advance_run(ends, &mut run_idx, idx.as_() + offset);
318            run_idx.as_()
319        })),
320        AllOr::None => unreachable!("AllInvalid indices have been handled earlier"),
321        AllOr::Some(validity) => {
322            // Invalid positions keep physical index zero, which is always in-bounds for the
323            // values and masked out by the result validity.
324            let mut physical_indices = BufferMut::zeroed(indices.len());
325            for (idx_pos, (is_valid, idx)) in validity.iter().zip(indices.iter()).enumerate() {
326                if !is_valid {
327                    continue;
328                }
329
330                advance_run(ends, &mut run_idx, idx.as_() + offset);
331                physical_indices[idx_pos] = run_idx.as_();
332            }
333            physical_indices.freeze()
334        }
335    }
336}
337
338/// Resolve indices through a dense logical-position-to-run-index table.
339///
340/// Building the table costs O(array_len), but every index then resolves with an unconditional
341/// gather, which beats per-index binary search and sort-then-merge for dense takes. Invalid
342/// indices may hold arbitrary values (even out of bounds), so they are redirected to position
343/// zero instead of branching; the result validity masks whatever they resolve to.
344fn physical_indices_table<I, T, O>(
345    ends: &[I],
346    offset: usize,
347    array_len: usize,
348    indices: &[T],
349    validity_mask: &Mask,
350) -> Buffer<O>
351where
352    I: UnsignedPType,
353    T: AsPrimitive<usize>,
354    O: UnsignedPType,
355    usize: AsPrimitive<O>,
356{
357    let table = run_index_table::<I, O>(ends, offset, array_len);
358    let table = table.as_slice();
359
360    match validity_mask.bit_buffer() {
361        AllOr::All => Buffer::from_trusted_len_iter(indices.iter().map(|idx| table[idx.as_()])),
362        AllOr::None => unreachable!("AllInvalid indices have been handled earlier"),
363        AllOr::Some(validity) => Buffer::from_trusted_len_iter(
364            validity
365                .iter()
366                .zip(indices.iter())
367                .map(|(is_valid, idx)| table[if is_valid { idx.as_() } else { 0 }]),
368        ),
369    }
370}
371
372/// Materialize the run index of every logical position in `[0, len)`.
373fn run_index_table<I, O>(ends: &[I], offset: usize, len: usize) -> Buffer<O>
374where
375    I: UnsignedPType,
376    O: UnsignedPType,
377    usize: AsPrimitive<O>,
378{
379    let mut table = BufferMut::with_capacity(len);
380    let mut run_start = 0;
381    for (run_idx, run_end) in trimmed_ends_iter(ends, offset, len).enumerate() {
382        table.push_n(run_idx.as_(), run_end - run_start);
383        run_start = run_end;
384    }
385    table.freeze()
386}
387
388fn physical_indices_linear_unsorted<I, T, O>(
389    ends: &[I],
390    offset: usize,
391    indices: &[T],
392    validity_mask: &Mask,
393    valid_count: usize,
394) -> Buffer<O>
395where
396    I: UnsignedPType,
397    T: AsPrimitive<usize>,
398    O: UnsignedPType,
399    usize: AsPrimitive<O>,
400{
401    let mut pairs = Vec::with_capacity(valid_count);
402    match validity_mask.bit_buffer() {
403        AllOr::All => {
404            pairs.extend(
405                indices
406                    .iter()
407                    .enumerate()
408                    .map(|(idx_pos, idx)| (idx.as_(), idx_pos)),
409            );
410        }
411        AllOr::None => unreachable!("AllInvalid indices have been handled earlier"),
412        AllOr::Some(validity) => {
413            for (idx_pos, (is_valid, idx)) in validity.iter().zip(indices.iter()).enumerate() {
414                if is_valid {
415                    pairs.push((idx.as_(), idx_pos));
416                }
417            }
418        }
419    }
420    pairs.sort_unstable();
421
422    let mut physical_indices = BufferMut::zeroed(indices.len());
423    let mut run_idx = 0;
424
425    for (idx, idx_pos) in pairs {
426        advance_run(ends, &mut run_idx, idx + offset);
427        physical_indices[idx_pos] = run_idx.as_();
428    }
429
430    physical_indices.freeze()
431}
432
433fn physical_indices_binary<I, T, O>(
434    ends: &[I],
435    offset: usize,
436    indices: &[T],
437    validity_mask: &Mask,
438) -> Buffer<O>
439where
440    I: UnsignedPType,
441    T: AsPrimitive<usize>,
442    O: UnsignedPType,
443    usize: AsPrimitive<O>,
444{
445    match validity_mask.bit_buffer() {
446        AllOr::All => Buffer::from_trusted_len_iter(
447            indices
448                .iter()
449                .map(|idx| physical_index_binary(ends, idx.as_() + offset).as_()),
450        ),
451        AllOr::None => Buffer::zeroed(indices.len()),
452        AllOr::Some(validity) => {
453            let mut physical_indices = BufferMut::zeroed(indices.len());
454            for (idx_pos, (is_valid, idx)) in validity.iter().zip(indices.iter()).enumerate() {
455                if !is_valid {
456                    continue;
457                }
458
459                physical_indices[idx_pos] = physical_index_binary(ends, idx.as_() + offset).as_();
460            }
461            physical_indices.freeze()
462        }
463    }
464}
465
466fn physical_index_binary<I: UnsignedPType>(ends: &[I], logical_idx: usize) -> usize {
467    let index = match <I as NumCast>::from(logical_idx) {
468        Some(logical_idx) => ends.partition_point(|end| *end <= logical_idx),
469        None => ends.len(),
470    };
471    index.min(ends.len() - 1)
472}
473
474fn advance_run<I: UnsignedPType>(ends: &[I], run_idx: &mut usize, logical_idx: usize) {
475    // A logical index that overflows the run-end type sits past every run, so it lands in the
476    // final run; otherwise advance while the current run ends at or before it.
477    let Some(logical_idx) = I::from(logical_idx) else {
478        *run_idx = ends.len().saturating_sub(1);
479        return;
480    };
481    while *run_idx + 1 < ends.len() && ends[*run_idx] <= logical_idx {
482        *run_idx += 1;
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use std::sync::LazyLock;
489
490    use rstest::rstest;
491    use vortex_array::ArrayRef;
492    use vortex_array::Canonical;
493    use vortex_array::IntoArray;
494    use vortex_array::VortexSessionExecute;
495    use vortex_array::arrays::BoolArray;
496    use vortex_array::arrays::PrimitiveArray;
497    use vortex_array::assert_arrays_eq;
498    use vortex_array::compute::conformance::take::test_take_conformance;
499    use vortex_array::validity::Validity;
500    use vortex_buffer::buffer;
501    use vortex_mask::Mask;
502    use vortex_session::VortexSession;
503
504    use super::physical_indices_binary;
505    use super::physical_indices_linear_sorted;
506    use super::physical_indices_linear_unsorted;
507    use super::physical_indices_table;
508    use crate::RunEnd;
509    use crate::RunEndArray;
510
511    static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
512        let session = vortex_array::array_session();
513        crate::initialize(&session);
514        session
515    });
516
517    fn ree_array() -> RunEndArray {
518        RunEnd::encode(
519            buffer![1, 1, 1, 4, 4, 4, 2, 2, 5, 5, 5, 5].into_array(),
520            &mut SESSION.create_execution_ctx(),
521        )
522        .unwrap()
523    }
524
525    #[test]
526    fn ree_take() {
527        let taken = ree_array().take(buffer![9, 8, 1, 3].into_array()).unwrap();
528        let expected = PrimitiveArray::from_iter(vec![5i32, 5, 1, 4]).into_array();
529        assert_arrays_eq!(taken, expected, &mut SESSION.create_execution_ctx());
530    }
531
532    #[test]
533    fn ree_take_end() {
534        let taken = ree_array().take(buffer![11].into_array()).unwrap();
535        let expected = PrimitiveArray::from_iter(vec![5i32]).into_array();
536        assert_arrays_eq!(taken, expected, &mut SESSION.create_execution_ctx());
537    }
538
539    #[test]
540    fn ree_take_sorted_boundaries() {
541        let taken = ree_array()
542            .take(buffer![0, 2, 3, 6, 8, 11].into_array())
543            .unwrap();
544        let expected = PrimitiveArray::from_iter(vec![1i32, 1, 4, 2, 5, 5]).into_array();
545        assert_arrays_eq!(taken, expected, &mut SESSION.create_execution_ctx());
546    }
547
548    #[test]
549    #[should_panic]
550    fn ree_take_out_of_bounds() {
551        let _array = ree_array()
552            .take(buffer![12].into_array())
553            .unwrap()
554            .execute::<Canonical>(&mut SESSION.create_execution_ctx())
555            .unwrap();
556    }
557
558    #[test]
559    fn sliced_take() {
560        let sliced = ree_array().slice(4..9).unwrap();
561        let taken = sliced.take(buffer![1, 3, 4].into_array()).unwrap();
562
563        let expected = PrimitiveArray::from_iter(vec![4i32, 2, 5]).into_array();
564        assert_arrays_eq!(taken, expected, &mut SESSION.create_execution_ctx());
565    }
566
567    #[test]
568    fn sliced_take_unsorted_dense() {
569        let sliced = ree_array().slice(4..9).unwrap();
570        let taken = sliced.take(buffer![4, 0, 2, 1].into_array()).unwrap();
571
572        let expected = PrimitiveArray::from_iter(vec![5i32, 4, 2, 4]).into_array();
573        assert_arrays_eq!(taken, expected, &mut SESSION.create_execution_ctx());
574    }
575
576    #[test]
577    fn ree_take_nullable() {
578        let taken = ree_array()
579            .take(PrimitiveArray::from_option_iter([Some(1), None]).into_array())
580            .unwrap();
581
582        let expected = PrimitiveArray::from_option_iter([Some(1i32), None]);
583        assert_arrays_eq!(
584            taken,
585            expected.into_array(),
586            &mut SESSION.create_execution_ctx()
587        );
588    }
589
590    #[test]
591    fn ree_take_all_null_indices() {
592        let taken = ree_array()
593            .take(PrimitiveArray::from_option_iter([None::<u64>, None]).into_array())
594            .unwrap();
595
596        let expected = PrimitiveArray::from_option_iter([None::<i32>, None]);
597        assert_arrays_eq!(
598            taken,
599            expected.into_array(),
600            &mut SESSION.create_execution_ctx()
601        );
602    }
603
604    #[test]
605    fn ree_take_null_index_skips_out_of_bounds_value() {
606        let indices = PrimitiveArray::new(
607            buffer![1u64, 12],
608            Validity::Array(BoolArray::from_iter([true, false]).into_array()),
609        );
610        let taken = ree_array().take(indices.into_array()).unwrap();
611
612        let expected = PrimitiveArray::from_option_iter([Some(1i32), None]);
613        assert_arrays_eq!(
614            taken,
615            expected.into_array(),
616            &mut SESSION.create_execution_ctx()
617        );
618    }
619
620    #[test]
621    fn ree_take_unsorted_null_index_skips_out_of_bounds_value() {
622        let indices = PrimitiveArray::new(
623            buffer![3u64, 12, 1],
624            Validity::Array(BoolArray::from_iter([true, false, true]).into_array()),
625        );
626        let taken = ree_array().take(indices.into_array()).unwrap();
627
628        let expected = PrimitiveArray::from_option_iter([Some(4i32), None, Some(1)]);
629        assert_arrays_eq!(
630            taken,
631            expected.into_array(),
632            &mut SESSION.create_execution_ctx()
633        );
634    }
635
636    #[test]
637    fn ree_take_dense_null_index_skips_out_of_bounds_value() {
638        let indices = PrimitiveArray::new(
639            buffer![0u64, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12],
640            Validity::Array(
641                BoolArray::from_iter([
642                    true, true, true, true, true, true, true, true, true, true, true, false,
643                ])
644                .into_array(),
645            ),
646        );
647        let taken = ree_array().take(indices.into_array()).unwrap();
648
649        let expected = PrimitiveArray::from_option_iter([
650            Some(1i32),
651            Some(1),
652            Some(1),
653            Some(4),
654            Some(4),
655            Some(4),
656            Some(2),
657            Some(2),
658            Some(5),
659            Some(5),
660            Some(5),
661            None,
662        ]);
663        assert_arrays_eq!(
664            taken,
665            expected.into_array(),
666            &mut SESSION.create_execution_ctx()
667        );
668    }
669
670    #[rstest]
671    #[case(vec![3u32, 6, 8, 12], 0, 12, vec![0u64, 11, 3, 3, 7, 2, 9], Mask::new_true(7))]
672    #[case(vec![3u32, 6, 8, 12], 0, 12, vec![5u64, 100, 2, 11, 0], Mask::from_indices(5, [0, 2, 3, 4]))]
673    #[case(vec![6u32, 8, 12], 4, 5, vec![4u64, 0, 2, 1, 3], Mask::new_true(5))]
674    fn unsorted_strategies_agree(
675        #[case] ends: Vec<u32>,
676        #[case] offset: usize,
677        #[case] len: usize,
678        #[case] indices: Vec<u64>,
679        #[case] mask: Mask,
680    ) {
681        let binary = physical_indices_binary::<u32, u64, u64>(&ends, offset, &indices, &mask);
682        let table = physical_indices_table::<u32, u64, u64>(&ends, offset, len, &indices, &mask);
683        let sort_merge = physical_indices_linear_unsorted::<u32, u64, u64>(
684            &ends,
685            offset,
686            &indices,
687            &mask,
688            mask.true_count(),
689        );
690
691        assert_eq!(binary.as_slice(), table.as_slice());
692        assert_eq!(binary.as_slice(), sort_merge.as_slice());
693    }
694
695    #[rstest]
696    #[case(vec![3u32, 6, 8, 12], 0, 12, vec![0u64, 2, 3, 6, 8, 11], Mask::new_true(6))]
697    #[case(vec![3u32, 6, 8, 12], 0, 12, vec![1u64, 100, 5, 9], Mask::from_indices(4, [0, 2, 3]))]
698    #[case(vec![6u32, 8, 12], 4, 5, vec![0u64, 1, 3, 4], Mask::new_true(4))]
699    fn sorted_strategies_agree(
700        #[case] ends: Vec<u32>,
701        #[case] offset: usize,
702        #[case] len: usize,
703        #[case] indices: Vec<u64>,
704        #[case] mask: Mask,
705    ) {
706        let binary = physical_indices_binary::<u32, u64, u64>(&ends, offset, &indices, &mask);
707        let table = physical_indices_table::<u32, u64, u64>(&ends, offset, len, &indices, &mask);
708        let sorted =
709            physical_indices_linear_sorted::<u32, u64, u64>(&ends, offset, &indices, &mask);
710
711        assert_eq!(binary.as_slice(), table.as_slice());
712        assert_eq!(binary.as_slice(), sorted.as_slice());
713    }
714
715    #[rstest]
716    #[case(ree_array())]
717    #[case(RunEnd::encode(
718        buffer![1u8, 1, 2, 2, 2, 3, 3, 3, 3, 4].into_array(),
719        &mut SESSION.create_execution_ctx(),
720    ).unwrap())]
721    #[case(RunEnd::encode(
722        PrimitiveArray::from_option_iter([
723            Some(10),
724            Some(10),
725            None,
726            None,
727            Some(20),
728            Some(20),
729            Some(20),
730        ])
731        .into_array(),
732        &mut SESSION.create_execution_ctx(),
733    ).unwrap())]
734    #[case(RunEnd::encode(buffer![42i32, 42, 42, 42, 42].into_array(),
735        &mut SESSION.create_execution_ctx())
736        .unwrap())]
737    #[case(RunEnd::encode(
738        buffer![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10].into_array(),
739        &mut SESSION.create_execution_ctx(),
740    ).unwrap())]
741    #[case({
742        let mut values = Vec::new();
743        for i in 0..20 {
744            for _ in 0..=i {
745                values.push(i);
746            }
747        }
748        RunEnd::encode(
749            PrimitiveArray::from_iter(values).into_array(),
750            &mut SESSION.create_execution_ctx(),
751        )
752        .unwrap()
753    })]
754    fn test_take_runend_conformance(#[case] array: RunEndArray) {
755        test_take_conformance(&array.into_array(), &mut SESSION.create_execution_ctx());
756    }
757
758    #[rstest]
759    #[case(ree_array().slice(3..6).unwrap())]
760    #[case({
761        let array = RunEnd::encode(
762            buffer![1i32, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3].into_array(),
763            &mut SESSION.create_execution_ctx(),
764        )
765        .unwrap();
766        array.slice(2..8).unwrap()
767    })]
768    fn test_take_sliced_runend_conformance(#[case] sliced: ArrayRef) {
769        test_take_conformance(&sliced, &mut SESSION.create_execution_ctx());
770    }
771}