@@ -10,15 +10,19 @@ use std::fmt::Formatter;
1010use std:: sync:: Arc ;
1111
1212use itertools:: Itertools ;
13+ use num_traits:: AsPrimitive ;
14+ use vortex_buffer:: BufferMut ;
1315use vortex_error:: VortexResult ;
1416use vortex_error:: vortex_bail;
1517use vortex_error:: vortex_ensure;
1618use vortex_error:: vortex_ensure_eq;
19+ use vortex_mask:: AllOr ;
1720use vortex_session:: registry:: CachedId ;
1821
1922use crate :: ArrayRef ;
2023use crate :: ExecutionCtx ;
2124use crate :: IntoArray ;
25+ use crate :: arrays:: List ;
2226use crate :: arrays:: ListArray ;
2327use crate :: arrays:: ListViewArray ;
2428use crate :: arrays:: PrimitiveArray ;
@@ -133,10 +137,7 @@ impl ScalarFnVTable for ListTransform {
133137 let ListTransformOptions :: Bound ( lambda) = options else {
134138 vortex_bail ! ( "list_transform()'s lambda is not bound" ) ;
135139 } ;
136- // Every element belongs to exactly one valid list, in order: nothing comes before the
137- // first list or after the last, and a null list has no elements.
138- let list = args. get ( 0 ) ?. execute :: < ListViewArray > ( ctx) ?;
139- let list = list_from_list_view ( empty_null_lists ( list, ctx) ?, ctx) ?;
140+ let list = exact_lists ( args. get ( 0 ) ?, ctx) ?;
140141 let elements = list. elements ( ) ;
141142 let values = lambda. evaluate ( vec ! [ elements. clone( ) ] , elements. len ( ) ) ?;
142143 vortex_ensure_eq ! ( values. len( ) , elements. len( ) ) ;
@@ -184,28 +185,74 @@ fn element_dtype(list: &DType, params: &[FieldName]) -> VortexResult<[DType; 1]>
184185 Ok ( [ element. as_ref ( ) . clone ( ) ] )
185186}
186187
188+ /// The lists laid out so that every element belongs to exactly one valid list, in order: nothing
189+ /// comes before the first list or after the last, and a null list has no elements.
190+ fn exact_lists ( list : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < ListArray > {
191+ // A list array usually is exact already, and then needs no rebuild.
192+ let list = match list. try_downcast :: < List > ( ) {
193+ Ok ( list) if is_exact ( & list, ctx) ? => return Ok ( list) ,
194+ Ok ( list) => list. into_array ( ) ,
195+ Err ( list) => list,
196+ } ;
197+ let list = list. execute :: < ListViewArray > ( ctx) ?;
198+ list_from_list_view ( empty_null_lists ( list, ctx) ?, ctx)
199+ }
200+
201+ /// Whether nothing comes before the first list or after the last, and no null list holds elements.
202+ fn is_exact ( list : & ListArray , ctx : & mut ExecutionCtx ) -> VortexResult < bool > {
203+ let offsets = list. offsets ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
204+ let nulls = null_runs ( & list. list_validity ( ) , list. len ( ) , ctx) ?;
205+ Ok ( match_each_integer_ptype ! ( offsets. ptype( ) , |O | {
206+ let offsets = offsets. as_slice:: <O >( ) ;
207+ let last: usize = offsets[ offsets. len( ) - 1 ] . as_( ) ;
208+ // The offsets are sorted, so a run of null lists is empty when it starts and ends at the
209+ // same offset.
210+ offsets[ 0 ] == O :: default ( )
211+ && last == list. elements( ) . len( )
212+ && nulls
213+ . iter( )
214+ . all( |& ( start, end) | offsets[ start] == offsets[ end] )
215+ } ) )
216+ }
217+
218+ /// The runs of null rows, as `(start, end)` ranges.
219+ fn null_runs (
220+ validity : & Validity ,
221+ len : usize ,
222+ ctx : & mut ExecutionCtx ,
223+ ) -> VortexResult < Vec < ( usize , usize ) > > {
224+ if matches ! ( validity, Validity :: NonNullable | Validity :: AllValid ) {
225+ return Ok ( Vec :: new ( ) ) ;
226+ }
227+ Ok ( match ( !& validity. execute_mask ( len, ctx) ?) . slices ( ) {
228+ AllOr :: All => vec ! [ ( 0 , len) ] ,
229+ AllOr :: None => Vec :: new ( ) ,
230+ AllOr :: Some ( runs) => runs. to_vec ( ) ,
231+ } )
232+ }
233+
187234/// The lists with every null list emptied, so that nothing under a null list reaches the lambda.
188235fn empty_null_lists ( list : ListViewArray , ctx : & mut ExecutionCtx ) -> VortexResult < ListViewArray > {
189236 let validity = list. listview_validity ( ) ;
190- if matches ! ( validity, Validity :: NonNullable | Validity :: AllValid ) {
237+ let nulls = null_runs ( & validity, list. len ( ) , ctx) ?;
238+ if nulls. is_empty ( ) {
191239 return Ok ( list) ;
192240 }
193- let valid = validity. execute_mask ( list. len ( ) , ctx) ?;
194241 let sizes = list. sizes ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
195242 let sizes = match_each_integer_ptype ! ( sizes. ptype( ) , |T | {
196243 let sizes = sizes. as_slice:: <T >( ) ;
197- let null = |index: usize | !valid. value( index) ;
198244 // Writers usually leave null lists empty already.
199- if !( 0 ..sizes. len( ) ) . any( |index| null( index) && sizes[ index] != T :: default ( ) ) {
245+ if nulls
246+ . iter( )
247+ . all( |& ( start, end) | sizes[ start..end] . iter( ) . all( |size| * size == T :: default ( ) ) )
248+ {
200249 return Ok ( list) ;
201250 }
202- PrimitiveArray :: from_iter( ( 0 ..sizes. len( ) ) . map( |index| {
203- if null( index) {
204- T :: default ( )
205- } else {
206- sizes[ index]
207- }
208- } ) )
251+ let mut sizes = BufferMut :: copy_from( sizes) ;
252+ for & ( start, end) in & nulls {
253+ sizes[ start..end] . fill( T :: default ( ) ) ;
254+ }
255+ PrimitiveArray :: new( sizes. freeze( ) , Validity :: NonNullable )
209256 } ) ;
210257 // SAFETY: only sizes changed, and only to 0, so every view stays within the elements. The new
211258 // array is not marked zero-copy to a list: an emptied list leaves its elements behind as a gap,
0 commit comments