@@ -23,10 +23,7 @@ use crate::arrays::list::ListArrayExt;
2323use crate :: arrays:: piecewise_sequence:: UnitMultiplierLengths ;
2424use crate :: arrays:: piecewise_sequence:: execute_unit_multiplier_index_arrays;
2525use crate :: arrays:: primitive:: PrimitiveArrayExt ;
26- use crate :: builders:: ArrayBuilder ;
27- use crate :: builders:: PrimitiveBuilder ;
2826use crate :: dtype:: IntegerPType ;
29- use crate :: dtype:: Nullability ;
3027use crate :: dtype:: UnsignedPType ;
3128use crate :: executor:: ExecutionCtx ;
3229use crate :: match_each_unsigned_integer_ptype;
@@ -64,7 +61,7 @@ impl TakeExecute for List {
6461 match_each_unsigned_integer_ptype ! ( offsets. ptype( ) , |O | {
6562 match_each_unsigned_integer_ptype!( indices. ptype( ) , |I | {
6663 match_smallest_offset_type!( total_approx, |OutputOffsetType | {
67- _take :: <I , O , OutputOffsetType >(
64+ take_with_piecewise_elements :: <I , O , OutputOffsetType >(
6865 array,
6966 offsets. as_view( ) ,
7067 indices. as_view( ) ,
@@ -77,7 +74,11 @@ impl TakeExecute for List {
7774 }
7875}
7976
80- fn _take < I : IntegerPType , O : IntegerPType , OutputOffsetType : IntegerPType > (
77+ fn take_with_piecewise_elements <
78+ I : IntegerPType ,
79+ O : IntegerPType ,
80+ OutputOffsetType : IntegerPType ,
81+ > (
8182 array : ArrayView < ' _ , List > ,
8283 offsets_array : ArrayView < ' _ , Primitive > ,
8384 indices_array : ArrayView < ' _ , Primitive > ,
@@ -91,56 +92,78 @@ fn _take<I: IntegerPType, O: IntegerPType, OutputOffsetType: IntegerPType>(
9192 . vortex_expect ( "Failed to compute validity mask" )
9293 . execute_mask ( indices_array. as_ref ( ) . len ( ) , ctx) ?;
9394
94- if !indices_validity. all_true ( ) || !data_validity. all_true ( ) {
95- return _take_nullable :: < I , O , OutputOffsetType > ( array, offsets_array, indices_array, ctx) ;
96- }
97-
9895 let offsets: & [ O ] = offsets_array. as_slice ( ) ;
9996 let indices: & [ I ] = indices_array. as_slice ( ) ;
10097
101- let mut new_offsets = PrimitiveBuilder :: < OutputOffsetType > :: with_capacity (
102- Nullability :: NonNullable ,
103- indices. len ( ) ,
104- ) ;
105- let mut elements_to_take =
106- PrimitiveBuilder :: with_capacity ( Nullability :: NonNullable , 2 * indices. len ( ) ) ;
98+ let offsets_capacity = indices
99+ . len ( )
100+ . checked_add ( 1 )
101+ . ok_or_else ( || vortex_err ! ( "List take offsets length overflow" ) ) ?;
102+ let mut new_offsets = BufferMut :: < OutputOffsetType > :: with_capacity ( offsets_capacity) ;
103+ let mut element_starts = BufferMut :: < u64 > :: with_capacity ( indices. len ( ) ) ;
104+ let mut element_lengths = BufferMut :: < u64 > :: with_capacity ( indices. len ( ) ) ;
105+
106+ let mut current_offset = 0usize ;
107+ new_offsets. push ( OutputOffsetType :: zero ( ) ) ;
107108
108- let mut current_offset = OutputOffsetType :: zero ( ) ;
109- new_offsets. append_zero ( ) ;
109+ for ( & data_idx, index_valid) in indices. iter ( ) . zip_eq ( indices_validity. iter ( ) ) {
110+ if !index_valid {
111+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
112+ element_starts. push ( 0 ) ;
113+ element_lengths. push ( 0 ) ;
114+ continue ;
115+ }
110116
111- for & data_idx in indices {
112117 let data_idx: usize = data_idx. as_ ( ) ;
113118
119+ if !data_validity. value ( data_idx) {
120+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
121+ element_starts. push ( 0 ) ;
122+ element_lengths. push ( 0 ) ;
123+ continue ;
124+ }
125+
114126 let start = offsets[ data_idx] ;
115127 let stop = offsets[ data_idx + 1 ] ;
128+ let start: usize = start. as_ ( ) ;
129+ let stop: usize = stop. as_ ( ) ;
130+ let length = stop
131+ . checked_sub ( start)
132+ . ok_or_else ( || vortex_err ! ( "List offsets are not monotonic at offset {stop}" ) ) ?;
116133
117- // Annoyingly, we can't turn (start..end) into a range, so we're doing that manually.
118- //
119- // We could convert start and end to usize, but that would impose a potentially
120- // harder constraint - now we don't care if they fit into usize as long as their
121- // difference does.
122- let additional: usize = ( stop - start) . as_ ( ) ;
123-
124- // TODO(0ax1): optimize this
125- elements_to_take. reserve_exact ( additional) ;
126- for i in 0 ..additional {
127- elements_to_take. append_value ( start + O :: from_usize ( i) . vortex_expect ( "i < additional" ) ) ;
128- }
129- current_offset +=
130- OutputOffsetType :: from_usize ( ( stop - start) . as_ ( ) ) . vortex_expect ( "offset conversion" ) ;
131- new_offsets. append_value ( current_offset) ;
134+ current_offset = current_offset
135+ . checked_add ( length)
136+ . ok_or_else ( || vortex_err ! ( "List take output elements length overflow" ) ) ?;
137+ new_offsets. push ( new_offset_value :: < OutputOffsetType > ( current_offset) ?) ;
138+ element_starts. push ( start as u64 ) ;
139+ element_lengths. push ( length as u64 ) ;
132140 }
133141
134- let elements_to_take = elements_to_take. finish ( ) ;
135- let new_offsets = new_offsets. finish ( ) ;
136-
137- let new_elements = array. elements ( ) . take ( elements_to_take) ?;
142+ let new_offsets = PrimitiveArray :: new ( new_offsets. freeze ( ) , Validity :: NonNullable ) . into_array ( ) ;
143+ let multipliers = ConstantArray :: new ( 1u64 , element_starts. len ( ) ) . into_array ( ) ;
138144
139- Ok ( ListArray :: try_new (
140- new_elements,
141- new_offsets,
142- array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
143- ) ?
145+ // SAFETY: valid source rows contribute ranges derived from list offsets; null index/source
146+ // rows contribute zero-length placeholder ranges. `current_offset` is the sum of all generated
147+ // element lengths, and multiplier 1 preserves contiguous ranges.
148+ let element_indices = unsafe {
149+ PiecewiseSequenceArray :: new_unchecked (
150+ element_starts. into_array ( ) ,
151+ element_lengths. into_array ( ) ,
152+ multipliers,
153+ current_offset,
154+ )
155+ } ;
156+ let new_elements = array. elements ( ) . take ( element_indices. into_array ( ) ) ?;
157+
158+ // SAFETY: offsets are rebuilt from the gathered element ranges and have one entry per output
159+ // row plus the leading zero; validity is produced by the usual take-validity path.
160+ Ok ( unsafe {
161+ ListArray :: new_unchecked (
162+ new_elements,
163+ new_offsets,
164+ array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
165+ )
166+ }
144167 . into_array ( ) )
145168}
146169
@@ -603,83 +626,6 @@ fn new_offset_value<T: IntegerPType>(value: usize) -> VortexResult<T> {
603626 } )
604627}
605628
606- // Kept out-of-line: as a single-callsite generic helper it would otherwise be inlined into every
607- // monomorphization of `_take`, duplicating the entire nullable path across all specializations.
608- #[ inline( never) ]
609- fn _take_nullable < I : IntegerPType , O : IntegerPType , OutputOffsetType : IntegerPType > (
610- array : ArrayView < ' _ , List > ,
611- offsets_array : ArrayView < ' _ , Primitive > ,
612- indices_array : ArrayView < ' _ , Primitive > ,
613- ctx : & mut ExecutionCtx ,
614- ) -> VortexResult < ArrayRef > {
615- let offsets: & [ O ] = offsets_array. as_slice ( ) ;
616- let indices: & [ I ] = indices_array. as_slice ( ) ;
617- let data_validity = array
618- . list_validity ( )
619- . execute_mask ( array. as_ref ( ) . len ( ) , ctx) ?;
620- let indices_validity = indices_array
621- . validity ( )
622- . vortex_expect ( "Failed to compute validity mask" )
623- . execute_mask ( indices_array. as_ref ( ) . len ( ) , ctx) ?;
624-
625- let mut new_offsets = PrimitiveBuilder :: < OutputOffsetType > :: with_capacity (
626- Nullability :: NonNullable ,
627- indices. len ( ) ,
628- ) ;
629-
630- // This will be the indices we push down to the child array to call `take` with.
631- //
632- // There are 2 things to note here:
633- // - We do not know how many elements we need to take from our child since lists are variable
634- // size: thus we arbitrarily choose a capacity of `2 * # of indices`.
635- // - The type of the primitive builder needs to fit the largest offset of the (parent)
636- // `ListArray`, so we make this `PrimitiveBuilder` generic over `O` (instead of `I`).
637- let mut elements_to_take =
638- PrimitiveBuilder :: < O > :: with_capacity ( Nullability :: NonNullable , 2 * indices. len ( ) ) ;
639-
640- let mut current_offset = OutputOffsetType :: zero ( ) ;
641- new_offsets. append_zero ( ) ;
642-
643- for ( data_idx, index_valid) in indices. iter ( ) . zip ( indices_validity. iter ( ) ) {
644- if !index_valid {
645- new_offsets. append_value ( current_offset) ;
646- continue ;
647- }
648-
649- let data_idx: usize = data_idx. as_ ( ) ;
650-
651- if !data_validity. value ( data_idx) {
652- new_offsets. append_value ( current_offset) ;
653- continue ;
654- }
655-
656- let start = offsets[ data_idx] ;
657- let stop = offsets[ data_idx + 1 ] ;
658-
659- // See the note in `_take` on the reasoning.
660- let additional: usize = ( stop - start) . as_ ( ) ;
661-
662- elements_to_take. reserve_exact ( additional) ;
663- for i in 0 ..additional {
664- elements_to_take. append_value ( start + O :: from_usize ( i) . vortex_expect ( "i < additional" ) ) ;
665- }
666- current_offset +=
667- OutputOffsetType :: from_usize ( ( stop - start) . as_ ( ) ) . vortex_expect ( "offset conversion" ) ;
668- new_offsets. append_value ( current_offset) ;
669- }
670-
671- let elements_to_take = elements_to_take. finish ( ) ;
672- let new_offsets = new_offsets. finish ( ) ;
673- let new_elements = array. elements ( ) . take ( elements_to_take) ?;
674-
675- Ok ( ListArray :: try_new (
676- new_elements,
677- new_offsets,
678- array. validity ( ) ?. take ( indices_array. array ( ) ) ?,
679- ) ?
680- . into_array ( ) )
681- }
682-
683629#[ cfg( test) ]
684630mod test {
685631 use std:: sync:: Arc ;
0 commit comments