@@ -24,10 +24,7 @@ use crate::arrays::list::ListArrayExt;
2424use crate :: arrays:: piecewise_sequence:: constant_unsigned_usize;
2525use crate :: arrays:: piecewise_sequence:: maybe_contiguous_slices;
2626use crate :: arrays:: primitive:: PrimitiveArrayExt ;
27- use crate :: builders:: ArrayBuilder ;
28- use crate :: builders:: PrimitiveBuilder ;
2927use crate :: dtype:: IntegerPType ;
30- use crate :: dtype:: Nullability ;
3128use crate :: dtype:: UnsignedPType ;
3229use crate :: executor:: ExecutionCtx ;
3330use crate :: match_each_unsigned_integer_ptype;
@@ -65,7 +62,7 @@ impl TakeExecute for List {
6562 match_each_unsigned_integer_ptype ! ( offsets. ptype( ) , |O | {
6663 match_each_unsigned_integer_ptype!( indices. ptype( ) , |I | {
6764 match_smallest_offset_type!( total_approx, |OutputOffsetType | {
68- _take :: <I , O , OutputOffsetType >(
65+ take_with_piecewise_elements :: <I , O , OutputOffsetType >(
6966 array,
7067 offsets. as_view( ) ,
7168 indices. as_view( ) ,
@@ -78,7 +75,11 @@ impl TakeExecute for List {
7875 }
7976}
8077
81- fn _take < I : IntegerPType , O : IntegerPType , OutputOffsetType : IntegerPType > (
78+ fn take_with_piecewise_elements <
79+ I : IntegerPType ,
80+ O : IntegerPType ,
81+ OutputOffsetType : IntegerPType ,
82+ > (
8283 array : ArrayView < ' _ , List > ,
8384 offsets_array : ArrayView < ' _ , Primitive > ,
8485 indices_array : ArrayView < ' _ , Primitive > ,
@@ -92,56 +93,78 @@ fn _take<I: IntegerPType, O: IntegerPType, OutputOffsetType: IntegerPType>(
9293 . vortex_expect ( "Failed to compute validity mask" )
9394 . execute_mask ( indices_array. as_ref ( ) . len ( ) , ctx) ?;
9495
95- if !indices_validity. all_true ( ) || !data_validity. all_true ( ) {
96- return _take_nullable :: < I , O , OutputOffsetType > ( array, offsets_array, indices_array, ctx) ;
97- }
98-
9996 let offsets: & [ O ] = offsets_array. as_slice ( ) ;
10097 let indices: & [ I ] = indices_array. as_slice ( ) ;
10198
102- let mut new_offsets = PrimitiveBuilder :: < OutputOffsetType > :: with_capacity (
103- Nullability :: NonNullable ,
104- indices. len ( ) ,
105- ) ;
106- let mut elements_to_take =
107- PrimitiveBuilder :: with_capacity ( Nullability :: NonNullable , 2 * indices. len ( ) ) ;
99+ let offsets_capacity = indices
100+ . len ( )
101+ . checked_add ( 1 )
102+ . ok_or_else ( || vortex_err ! ( "List take offsets length overflow" ) ) ?;
103+ let mut new_offsets = BufferMut :: < OutputOffsetType > :: with_capacity ( offsets_capacity) ;
104+ let mut element_starts = BufferMut :: < u64 > :: with_capacity ( indices. len ( ) ) ;
105+ let mut element_lengths = BufferMut :: < u64 > :: with_capacity ( indices. len ( ) ) ;
106+
107+ let mut current_offset = 0usize ;
108+ new_offsets. push ( OutputOffsetType :: zero ( ) ) ;
108109
109- let mut current_offset = OutputOffsetType :: zero ( ) ;
110- new_offsets. append_zero ( ) ;
110+ for ( & data_idx, index_valid) in indices. iter ( ) . zip_eq ( indices_validity. iter ( ) ) {
111+ if !index_valid {
112+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
113+ element_starts. push ( 0 ) ;
114+ element_lengths. push ( 0 ) ;
115+ continue ;
116+ }
111117
112- for & data_idx in indices {
113118 let data_idx: usize = data_idx. as_ ( ) ;
114119
120+ if !data_validity. value ( data_idx) {
121+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
122+ element_starts. push ( 0 ) ;
123+ element_lengths. push ( 0 ) ;
124+ continue ;
125+ }
126+
115127 let start = offsets[ data_idx] ;
116128 let stop = offsets[ data_idx + 1 ] ;
129+ let start: usize = start. as_ ( ) ;
130+ let stop: usize = stop. as_ ( ) ;
131+ let length = stop
132+ . checked_sub ( start)
133+ . ok_or_else ( || vortex_err ! ( "List offsets are not monotonic at offset {stop}" ) ) ?;
117134
118- // Annoyingly, we can't turn (start..end) into a range, so we're doing that manually.
119- //
120- // We could convert start and end to usize, but that would impose a potentially
121- // harder constraint - now we don't care if they fit into usize as long as their
122- // difference does.
123- let additional: usize = ( stop - start) . as_ ( ) ;
124-
125- // TODO(0ax1): optimize this
126- elements_to_take. reserve_exact ( additional) ;
127- for i in 0 ..additional {
128- elements_to_take. append_value ( start + O :: from_usize ( i) . vortex_expect ( "i < additional" ) ) ;
129- }
130- current_offset +=
131- OutputOffsetType :: from_usize ( ( stop - start) . as_ ( ) ) . vortex_expect ( "offset conversion" ) ;
132- new_offsets. append_value ( current_offset) ;
135+ current_offset = current_offset
136+ . checked_add ( length)
137+ . ok_or_else ( || vortex_err ! ( "List take output elements length overflow" ) ) ?;
138+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
139+ element_starts. push ( start as u64 ) ;
140+ element_lengths. push ( length as u64 ) ;
133141 }
134142
135- let elements_to_take = elements_to_take. finish ( ) ;
136- let new_offsets = new_offsets. finish ( ) ;
137-
138- let new_elements = array. elements ( ) . take ( elements_to_take) ?;
143+ let new_offsets = PrimitiveArray :: new ( new_offsets. freeze ( ) , Validity :: NonNullable ) . into_array ( ) ;
144+ let multipliers = ConstantArray :: new ( 1u64 , element_starts. len ( ) ) . into_array ( ) ;
139145
140- Ok ( ListArray :: try_new (
141- new_elements,
142- new_offsets,
143- array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
144- ) ?
146+ // SAFETY: valid source rows contribute ranges derived from list offsets; null index/source
147+ // rows contribute zero-length placeholder ranges. `current_offset` is the sum of all generated
148+ // element lengths, and multiplier 1 preserves contiguous ranges.
149+ let element_indices = unsafe {
150+ PiecewiseSequenceArray :: new_unchecked (
151+ element_starts. into_array ( ) ,
152+ element_lengths. into_array ( ) ,
153+ multipliers,
154+ current_offset,
155+ )
156+ } ;
157+ let new_elements = array. elements ( ) . take ( element_indices. into_array ( ) ) ?;
158+
159+ // SAFETY: offsets are rebuilt from the gathered element ranges and have one entry per output
160+ // row plus the leading zero; validity is produced by the usual take-validity path.
161+ Ok ( unsafe {
162+ ListArray :: new_unchecked (
163+ new_elements,
164+ new_offsets,
165+ array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
166+ )
167+ }
145168 . into_array ( ) )
146169}
147170
@@ -577,83 +600,6 @@ fn new_offset_value<T: IntegerPType>(value: usize) -> T {
577600 T :: from_usize ( value) . vortex_expect ( "output offset fits selected offset type" )
578601}
579602
580- // Kept out-of-line: as a single-callsite generic helper it would otherwise be inlined into every
581- // monomorphization of `_take`, duplicating the entire nullable path across all specializations.
582- #[ inline( never) ]
583- fn _take_nullable < I : IntegerPType , O : IntegerPType , OutputOffsetType : IntegerPType > (
584- array : ArrayView < ' _ , List > ,
585- offsets_array : ArrayView < ' _ , Primitive > ,
586- indices_array : ArrayView < ' _ , Primitive > ,
587- ctx : & mut ExecutionCtx ,
588- ) -> VortexResult < ArrayRef > {
589- let offsets: & [ O ] = offsets_array. as_slice ( ) ;
590- let indices: & [ I ] = indices_array. as_slice ( ) ;
591- let data_validity = array
592- . list_validity ( )
593- . execute_mask ( array. as_ref ( ) . len ( ) , ctx) ?;
594- let indices_validity = indices_array
595- . validity ( )
596- . vortex_expect ( "Failed to compute validity mask" )
597- . execute_mask ( indices_array. as_ref ( ) . len ( ) , ctx) ?;
598-
599- let mut new_offsets = PrimitiveBuilder :: < OutputOffsetType > :: with_capacity (
600- Nullability :: NonNullable ,
601- indices. len ( ) ,
602- ) ;
603-
604- // This will be the indices we push down to the child array to call `take` with.
605- //
606- // There are 2 things to note here:
607- // - We do not know how many elements we need to take from our child since lists are variable
608- // size: thus we arbitrarily choose a capacity of `2 * # of indices`.
609- // - The type of the primitive builder needs to fit the largest offset of the (parent)
610- // `ListArray`, so we make this `PrimitiveBuilder` generic over `O` (instead of `I`).
611- let mut elements_to_take =
612- PrimitiveBuilder :: < O > :: with_capacity ( Nullability :: NonNullable , 2 * indices. len ( ) ) ;
613-
614- let mut current_offset = OutputOffsetType :: zero ( ) ;
615- new_offsets. append_zero ( ) ;
616-
617- for ( data_idx, index_valid) in indices. iter ( ) . zip ( indices_validity. iter ( ) ) {
618- if !index_valid {
619- new_offsets. append_value ( current_offset) ;
620- continue ;
621- }
622-
623- let data_idx: usize = data_idx. as_ ( ) ;
624-
625- if !data_validity. value ( data_idx) {
626- new_offsets. append_value ( current_offset) ;
627- continue ;
628- }
629-
630- let start = offsets[ data_idx] ;
631- let stop = offsets[ data_idx + 1 ] ;
632-
633- // See the note in `_take` on the reasoning.
634- let additional: usize = ( stop - start) . as_ ( ) ;
635-
636- elements_to_take. reserve_exact ( additional) ;
637- for i in 0 ..additional {
638- elements_to_take. append_value ( start + O :: from_usize ( i) . vortex_expect ( "i < additional" ) ) ;
639- }
640- current_offset +=
641- OutputOffsetType :: from_usize ( ( stop - start) . as_ ( ) ) . vortex_expect ( "offset conversion" ) ;
642- new_offsets. append_value ( current_offset) ;
643- }
644-
645- let elements_to_take = elements_to_take. finish ( ) ;
646- let new_offsets = new_offsets. finish ( ) ;
647- let new_elements = array. elements ( ) . take ( elements_to_take) ?;
648-
649- Ok ( ListArray :: try_new (
650- new_elements,
651- new_offsets,
652- array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
653- ) ?
654- . into_array ( ) )
655- }
656-
657603#[ cfg( test) ]
658604mod test {
659605 use std:: sync:: Arc ;
0 commit comments