@@ -11,6 +11,7 @@ use std::sync::Arc;
1111
1212use itertools:: Itertools ;
1313use num_traits:: AsPrimitive ;
14+ use vortex_buffer:: Buffer ;
1415use vortex_buffer:: BufferMut ;
1516use vortex_error:: VortexResult ;
1617use vortex_error:: vortex_bail;
@@ -30,6 +31,7 @@ use crate::arrays::list::ListArrayExt;
3031use crate :: arrays:: list:: ListArraySlotsExt ;
3132use crate :: arrays:: listview:: ListViewArrayExt ;
3233use crate :: arrays:: listview:: ListViewArraySlotsExt ;
34+ use crate :: arrays:: listview:: ListViewRebuildMode ;
3335use crate :: arrays:: listview:: list_from_list_view;
3436use crate :: dtype:: DType ;
3537use crate :: dtype:: FieldName ;
@@ -47,7 +49,8 @@ use crate::validity::Validity;
4749/// Apply a lambda to every element of each list, keeping each list's length and validity.
4850///
4951/// The lambda takes one parameter, the element. The only child is the list, of `List` dtype in any
50- /// encoding, and the result is a `List`. The lambda runs only on the elements of valid lists.
52+ /// encoding, and the result is a `List` of the same shape. A lambda that can fail runs only on the
53+ /// elements of valid lists.
5154#[ derive( Clone ) ]
5255pub struct ListTransform ;
5356
@@ -137,15 +140,12 @@ impl ScalarFnVTable for ListTransform {
137140 let ListTransformOptions :: Bound ( lambda) = options else {
138141 vortex_bail ! ( "list_transform()'s lambda is not bound" ) ;
139142 } ;
140- let list = exact_lists ( args. get ( 0 ) ?, ctx) ?;
141- let elements = list. elements ( ) ;
142- let values = lambda. evaluate ( vec ! [ elements. clone( ) ] , elements. len ( ) ) ?;
143- vortex_ensure_eq ! ( values. len( ) , elements. len( ) ) ;
144- // SAFETY: the offsets are the list's, over one value per element.
145- Ok ( unsafe {
146- ListArray :: new_unchecked ( values, list. offsets ( ) . clone ( ) , list. list_validity ( ) )
147- }
148- . into_array ( ) )
143+ let list = if lambda. is_infallible ( ) {
144+ Lists :: as_they_are ( args. get ( 0 ) ?, ctx) ?
145+ } else {
146+ Lists :: visible_elements_only ( args. get ( 0 ) ?, ctx) ?
147+ } ;
148+ list. with_elements ( |elements| lambda. evaluate ( vec ! [ elements. clone( ) ] , elements. len ( ) ) )
149149 }
150150
151151 fn is_strict ( & self , _options : & ListTransformOptions ) -> bool {
@@ -185,34 +185,113 @@ fn element_dtype(list: &DType, params: &[FieldName]) -> VortexResult<[DType; 1]>
185185 Ok ( [ element. as_ref ( ) . clone ( ) ] )
186186}
187187
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)
188+ /// The lists the lambda runs over: their elements buffer, and the offsets (and sizes) that put the
189+ /// results back into lists of the same shape.
190+ enum Lists {
191+ List ( ListArray ) ,
192+ View ( ListViewArray ) ,
199193}
200194
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 > {
195+ impl Lists {
196+ /// The lists as they are. For a lambda that can't fail: it runs over the whole elements buffer,
197+ /// and results for elements no valid list shows are never read.
198+ fn as_they_are ( list : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
199+ Ok ( match list. try_downcast :: < List > ( ) {
200+ Ok ( list) => Self :: List ( list) ,
201+ Err ( list) => Self :: View ( list. execute :: < ListViewArray > ( ctx) ?) ,
202+ } )
203+ }
204+
205+ /// The lists with nothing in their elements buffer but the elements of valid lists. For a lambda
206+ /// that can fail, which must not fail on a value nobody sees.
207+ ///
208+ /// The lists keep their shape, trimmed of what comes before the first list and after the last,
209+ /// unless a null list holds elements or a list view's lists aren't laid out back to back. Only
210+ /// then are the elements copied.
211+ fn visible_elements_only ( list : ArrayRef , ctx : & mut ExecutionCtx ) -> VortexResult < Self > {
212+ let list = match list. try_downcast :: < List > ( ) {
213+ Ok ( list) => match trim_list ( & list, ctx) ? {
214+ Some ( trimmed) => return Ok ( Self :: List ( trimmed) ) ,
215+ None => list. into_array ( ) ,
216+ } ,
217+ Err ( list) => list,
218+ } ;
219+ let view = list. execute :: < ListViewArray > ( ctx) ?;
220+ match empty_null_lists ( & view, ctx) ? {
221+ None if view. is_zero_copy_to_list ( ) => Ok ( Self :: View (
222+ view. rebuild ( ListViewRebuildMode :: TrimElements , ctx) ?,
223+ ) ) ,
224+ None => Ok ( Self :: List ( list_from_list_view ( view, ctx) ?) ) ,
225+ Some ( emptied) => Ok ( Self :: List ( list_from_list_view ( emptied, ctx) ?) ) ,
226+ }
227+ }
228+
229+ /// The same lists, with `f` of their elements buffer as their elements.
230+ fn with_elements (
231+ self ,
232+ f : impl FnOnce ( & ArrayRef ) -> VortexResult < ArrayRef > ,
233+ ) -> VortexResult < ArrayRef > {
234+ match self {
235+ Self :: List ( list) => {
236+ let values = f ( list. elements ( ) ) ?;
237+ vortex_ensure_eq ! ( values. len( ) , list. elements( ) . len( ) ) ;
238+ // SAFETY: the offsets and validity are the list's, over one value per element.
239+ Ok ( unsafe {
240+ ListArray :: new_unchecked ( values, list. offsets ( ) . clone ( ) , list. list_validity ( ) )
241+ }
242+ . into_array ( ) )
243+ }
244+ Self :: View ( view) => {
245+ let values = f ( view. elements ( ) ) ?;
246+ vortex_ensure_eq ! ( values. len( ) , view. elements( ) . len( ) ) ;
247+ // SAFETY: the offsets, sizes and validity are the view's, over one value per
248+ // element, so the views keep their bounds and their layout.
249+ Ok ( unsafe {
250+ ListViewArray :: new_unchecked (
251+ values,
252+ view. offsets ( ) . clone ( ) ,
253+ view. sizes ( ) . clone ( ) ,
254+ view. listview_validity ( ) ,
255+ )
256+ . with_zero_copy_to_list ( view. is_zero_copy_to_list ( ) )
257+ }
258+ . into_array ( ) )
259+ }
260+ }
261+ }
262+ }
263+
264+ /// The list without what comes before its first list and after its last, or `None` when a null
265+ /// list holds elements, which only a rebuild can drop.
266+ fn trim_list ( list : & ListArray , ctx : & mut ExecutionCtx ) -> VortexResult < Option < ListArray > > {
203267 let offsets = list. offsets ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
204268 let nulls = null_runs ( & list. list_validity ( ) , list. len ( ) , ctx) ?;
205- Ok ( match_each_integer_ptype ! ( offsets. ptype( ) , |O | {
269+ match_each_integer_ptype ! ( offsets. ptype( ) , |O | {
206270 let offsets = offsets. as_slice:: <O >( ) ;
207- let last: usize = offsets[ offsets. len( ) - 1 ] . as_( ) ;
208271 // The offsets are sorted, so a run of null lists is empty when it starts and ends at the
209272 // 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- } ) )
273+ if nulls
274+ . iter( )
275+ . any( |& ( start, end) | offsets[ start] != offsets[ end] )
276+ {
277+ return Ok ( None ) ;
278+ }
279+ let first = offsets[ 0 ] ;
280+ let ( start, end) : ( usize , usize ) = ( first. as_( ) , offsets[ offsets. len( ) - 1 ] . as_( ) ) ;
281+ if start == 0 && end == list. elements( ) . len( ) {
282+ return Ok ( Some ( list. clone( ) ) ) ;
283+ }
284+ let shifted: Buffer <O > = offsets. iter( ) . map( |& offset| offset - first) . collect( ) ;
285+ // SAFETY: the elements are cut to the lists' range and the offsets shifted by its start,
286+ // so every list keeps its elements.
287+ Ok ( Some ( unsafe {
288+ ListArray :: new_unchecked(
289+ list. elements( ) . slice( start..end) ?,
290+ shifted. into_array( ) ,
291+ list. list_validity( ) ,
292+ )
293+ } ) )
294+ } )
216295}
217296
218297/// The runs of null rows, as `(start, end)` ranges.
@@ -231,12 +310,16 @@ fn null_runs(
231310 } )
232311}
233312
234- /// The lists with every null list emptied, so that nothing under a null list reaches the lambda.
235- fn empty_null_lists ( list : ListViewArray , ctx : & mut ExecutionCtx ) -> VortexResult < ListViewArray > {
313+ /// The lists with every null list emptied, so that nothing under a null list reaches the lambda, or
314+ /// `None` when no null list holds elements.
315+ fn empty_null_lists (
316+ list : & ListViewArray ,
317+ ctx : & mut ExecutionCtx ,
318+ ) -> VortexResult < Option < ListViewArray > > {
236319 let validity = list. listview_validity ( ) ;
237320 let nulls = null_runs ( & validity, list. len ( ) , ctx) ?;
238321 if nulls. is_empty ( ) {
239- return Ok ( list ) ;
322+ return Ok ( None ) ;
240323 }
241324 let sizes = list. sizes ( ) . clone ( ) . execute :: < PrimitiveArray > ( ctx) ?;
242325 let sizes = match_each_integer_ptype ! ( sizes. ptype( ) , |T | {
@@ -246,7 +329,7 @@ fn empty_null_lists(list: ListViewArray, ctx: &mut ExecutionCtx) -> VortexResult
246329 . iter( )
247330 . all( |& ( start, end) | sizes[ start..end] . iter( ) . all( |size| * size == T :: default ( ) ) )
248331 {
249- return Ok ( list ) ;
332+ return Ok ( None ) ;
250333 }
251334 let mut sizes = BufferMut :: copy_from( sizes) ;
252335 for & ( start, end) in & nulls {
@@ -257,12 +340,12 @@ fn empty_null_lists(list: ListViewArray, ctx: &mut ExecutionCtx) -> VortexResult
257340 // SAFETY: only sizes changed, and only to 0, so every view stays within the elements. The new
258341 // array is not marked zero-copy to a list: an emptied list leaves its elements behind as a gap,
259342 // which the rebuild must drop.
260- Ok ( unsafe {
343+ Ok ( Some ( unsafe {
261344 ListViewArray :: new_unchecked (
262345 list. elements ( ) . clone ( ) ,
263346 list. offsets ( ) . clone ( ) ,
264347 sizes. into_array ( ) ,
265348 validity,
266349 )
267- } )
350+ } ) )
268351}
0 commit comments