Skip to content

Commit bf4e84e

Browse files
authored
Feature: add append_array_as_list methods to the list arrays (#5707)
Adds `append_array_as_list` methods to the list arrays. This makes it possible to circumvent having to use `ListScalar`s to build `ListArray`s (or any of the other list array kinds). cc @nrc Let me know if this helps your usecase! Signed-off-by: Connor Tsui <[email protected]>
1 parent e3eabde commit bf4e84e

File tree

3 files changed

+250
-0
lines changed

3 files changed

+250
-0
lines changed

vortex-array/src/builders/fixed_size_list.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,33 @@ impl FixedSizeListBuilder {
7373
}
7474
}
7575

76+
/// Appends an array as a single non-null list entry to the builder.
77+
///
78+
/// The input `array` must have the same dtype as the element dtype of this list builder, and
79+
/// its length must match the fixed list size.
80+
///
81+
/// Note that the list entry will be non-null but the elements themselves are allowed to be null
82+
/// (only if the elements [`DType`] is nullable, of course).
83+
pub fn append_array_as_list(&mut self, array: &dyn Array) -> VortexResult<()> {
84+
vortex_ensure!(
85+
array.dtype() == self.element_dtype(),
86+
"Array dtype {:?} does not match list element dtype {:?}",
87+
array.dtype(),
88+
self.element_dtype()
89+
);
90+
vortex_ensure!(
91+
array.len() == self.list_size() as usize,
92+
"Array length {} does not match fixed list size {}",
93+
array.len(),
94+
self.list_size()
95+
);
96+
97+
self.elements_builder.extend_from_array(array);
98+
self.nulls.append_non_null();
99+
100+
Ok(())
101+
}
102+
76103
/// Appends a fixed-size list `value` to the builder.
77104
///
78105
/// Note that a [`ListScalar`] can represent both a [`ListArray`] scalar **and** a
@@ -770,4 +797,63 @@ mod tests {
770797
let wrong_scalar = Scalar::from(42i32);
771798
assert!(builder.append_scalar(&wrong_scalar).is_err());
772799
}
800+
801+
#[test]
802+
fn test_append_array_as_list() {
803+
let dtype: Arc<DType> = Arc::new(I32.into());
804+
let mut builder = FixedSizeListBuilder::with_capacity(dtype.clone(), 3, NonNullable, 10);
805+
806+
// Append a primitive array as a single list entry.
807+
let arr1 = buffer![1i32, 2, 3].into_array();
808+
builder.append_array_as_list(&arr1).unwrap();
809+
810+
// Interleave with a list scalar.
811+
builder
812+
.append_value(
813+
Scalar::fixed_size_list(
814+
dtype.clone(),
815+
vec![10i32.into(), 11i32.into(), 12i32.into()],
816+
NonNullable,
817+
)
818+
.as_list(),
819+
)
820+
.unwrap();
821+
822+
// Append another primitive array as a single list entry.
823+
let arr2 = buffer![4i32, 5, 6].into_array();
824+
builder.append_array_as_list(&arr2).unwrap();
825+
826+
// Interleave with another list scalar.
827+
builder
828+
.append_value(
829+
Scalar::fixed_size_list(
830+
dtype.clone(),
831+
vec![20i32.into(), 21i32.into(), 22i32.into()],
832+
NonNullable,
833+
)
834+
.as_list(),
835+
)
836+
.unwrap();
837+
838+
let fsl = builder.finish_into_fixed_size_list();
839+
assert_eq!(fsl.len(), 4);
840+
assert_eq!(fsl.list_size(), 3);
841+
842+
// Verify elements array: [1, 2, 3, 10, 11, 12, 4, 5, 6, 20, 21, 22].
843+
let elements = fsl.elements().to_primitive();
844+
assert_eq!(
845+
elements.as_slice::<i32>(),
846+
&[1, 2, 3, 10, 11, 12, 4, 5, 6, 20, 21, 22]
847+
);
848+
849+
// Test dtype mismatch error.
850+
let mut builder = FixedSizeListBuilder::with_capacity(dtype.clone(), 3, NonNullable, 10);
851+
let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
852+
assert!(builder.append_array_as_list(&wrong_dtype_arr).is_err());
853+
854+
// Test length mismatch error.
855+
let mut builder = FixedSizeListBuilder::with_capacity(dtype, 3, NonNullable, 10);
856+
let wrong_len_arr = buffer![1i32, 2].into_array();
857+
assert!(builder.append_array_as_list(&wrong_len_arr).is_err());
858+
}
773859
}

vortex-array/src/builders/list.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ impl<O: IntegerPType> ListBuilder<O> {
8787
}
8888
}
8989

90+
/// Appends an array as a single non-null list entry to the builder.
91+
///
92+
/// The input `array` must have the same dtype as the element dtype of this list builder.
93+
///
94+
/// Note that the list entry will be non-null but the elements themselves are allowed to be null
95+
/// (only if the elements [`DType`] in nullable, of course).
96+
pub fn append_array_as_list(&mut self, array: &dyn Array) -> VortexResult<()> {
97+
vortex_ensure!(
98+
array.dtype() == self.element_dtype(),
99+
"Array dtype {:?} does not match list element dtype {:?}",
100+
array.dtype(),
101+
self.element_dtype()
102+
);
103+
104+
self.elements_builder.extend_from_array(array);
105+
self.nulls.append_non_null();
106+
self.offsets_builder.append_value(
107+
O::from_usize(self.elements_builder.len())
108+
.vortex_expect("Failed to convert from usize to O"),
109+
);
110+
111+
Ok(())
112+
}
113+
90114
/// Appends a list `value` to the builder.
91115
pub fn append_value(&mut self, value: ListScalar) -> VortexResult<()> {
92116
match value.elements() {
@@ -547,4 +571,51 @@ mod tests {
547571
let wrong_scalar = Scalar::from(42i32);
548572
assert!(builder.append_scalar(&wrong_scalar).is_err());
549573
}
574+
575+
#[test]
576+
fn test_append_array_as_list() {
577+
let dtype: Arc<DType> = Arc::new(I32.into());
578+
let mut builder = ListBuilder::<u32>::with_capacity(dtype.clone(), NonNullable, 20, 10);
579+
580+
// Append a primitive array as a single list entry.
581+
let arr1 = buffer![1i32, 2, 3].into_array();
582+
builder.append_array_as_list(&arr1).unwrap();
583+
584+
// Interleave with a list scalar.
585+
builder
586+
.append_value(
587+
Scalar::list(dtype.clone(), vec![10i32.into(), 11i32.into()], NonNullable)
588+
.as_list(),
589+
)
590+
.unwrap();
591+
592+
// Append another primitive array as a single list entry.
593+
let arr2 = buffer![4i32, 5].into_array();
594+
builder.append_array_as_list(&arr2).unwrap();
595+
596+
// Append an empty array as a single list entry (empty list).
597+
let arr3 = buffer![0i32; 0].into_array();
598+
builder.append_array_as_list(&arr3).unwrap();
599+
600+
// Interleave with another list scalar (empty list).
601+
builder
602+
.append_value(Scalar::list_empty(dtype.clone(), NonNullable).as_list())
603+
.unwrap();
604+
605+
let list = builder.finish_into_list();
606+
assert_eq!(list.len(), 5);
607+
608+
// Verify elements array: [1, 2, 3, 10, 11, 4, 5].
609+
let elements = list.elements().to_primitive();
610+
assert_eq!(elements.as_slice::<i32>(), &[1, 2, 3, 10, 11, 4, 5]);
611+
612+
// Verify offsets array.
613+
let offsets = list.offsets().to_primitive();
614+
assert_eq!(offsets.as_slice::<u32>(), &[0, 3, 5, 7, 7, 7]);
615+
616+
// Test dtype mismatch error.
617+
let mut builder = ListBuilder::<u32>::with_capacity(dtype, NonNullable, 20, 10);
618+
let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
619+
assert!(builder.append_array_as_list(&wrong_dtype_arr).is_err());
620+
}
550621
}

vortex-array/src/builders/listview.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,43 @@ impl<O: IntegerPType, S: IntegerPType> ListViewBuilder<O, S> {
110110
}
111111
}
112112

113+
/// Appends an array as a single non-null list entry to the builder.
114+
///
115+
/// The input `array` must have the same dtype as the element dtype of this list builder.
116+
///
117+
/// Note that the list entry will be non-null but the elements themselves are allowed to be null
118+
/// (only if the elements [`DType`] is nullable, of course).
119+
pub fn append_array_as_list(&mut self, array: &dyn Array) -> VortexResult<()> {
120+
vortex_ensure!(
121+
array.dtype() == self.element_dtype(),
122+
"Array dtype {:?} does not match list element dtype {:?}",
123+
array.dtype(),
124+
self.element_dtype()
125+
);
126+
127+
let curr_offset = self.elements_builder.len();
128+
let num_elements = array.len();
129+
130+
// We must assert this even in release mode to ensure that the safety comment in
131+
// `finish_into_listview` is correct.
132+
assert!(
133+
((curr_offset + num_elements) as u64) < O::max_value_as_u64(),
134+
"appending this list would cause an offset overflow"
135+
);
136+
137+
self.elements_builder.extend_from_array(array);
138+
self.nulls.append_non_null();
139+
140+
self.offsets_builder.append_value(
141+
O::from_usize(curr_offset).vortex_expect("Failed to convert from usize to `O`"),
142+
);
143+
self.sizes_builder.append_value(
144+
S::from_usize(num_elements).vortex_expect("Failed to convert from usize to `S`"),
145+
);
146+
147+
Ok(())
148+
}
149+
113150
/// Append a list of values to the builder.
114151
///
115152
/// This method extends the value builder with the provided values and records
@@ -633,4 +670,60 @@ mod tests {
633670
.contains("null value to non-nullable")
634671
);
635672
}
673+
674+
#[test]
675+
fn test_append_array_as_list() {
676+
use vortex_buffer::buffer;
677+
678+
use crate::ToCanonical;
679+
680+
let dtype: Arc<DType> = Arc::new(I32.into());
681+
let mut builder =
682+
ListViewBuilder::<u32, u32>::with_capacity(dtype.clone(), NonNullable, 20, 10);
683+
684+
// Append a primitive array as a single list entry.
685+
let arr1 = buffer![1i32, 2, 3].into_array();
686+
builder.append_array_as_list(&arr1).unwrap();
687+
688+
// Interleave with a list scalar.
689+
builder
690+
.append_value(
691+
Scalar::list(dtype.clone(), vec![10i32.into(), 11i32.into()], NonNullable)
692+
.as_list(),
693+
)
694+
.unwrap();
695+
696+
// Append another primitive array as a single list entry.
697+
let arr2 = buffer![4i32, 5].into_array();
698+
builder.append_array_as_list(&arr2).unwrap();
699+
700+
// Append an empty array as a single list entry (empty list).
701+
let arr3 = buffer![0i32; 0].into_array();
702+
builder.append_array_as_list(&arr3).unwrap();
703+
704+
// Interleave with another list scalar.
705+
builder
706+
.append_value(Scalar::list_empty(dtype.clone(), NonNullable).as_list())
707+
.unwrap();
708+
709+
let listview = builder.finish_into_listview();
710+
assert_eq!(listview.len(), 5);
711+
712+
// Verify elements array: [1, 2, 3, 10, 11, 4, 5].
713+
let elements = listview.elements().to_primitive();
714+
assert_eq!(elements.as_slice::<i32>(), &[1, 2, 3, 10, 11, 4, 5]);
715+
716+
// Verify offsets array.
717+
let offsets = listview.offsets().to_primitive();
718+
assert_eq!(offsets.as_slice::<u32>(), &[0, 3, 5, 7, 7]);
719+
720+
// Verify sizes array.
721+
let sizes = listview.sizes().to_primitive();
722+
assert_eq!(sizes.as_slice::<u32>(), &[3, 2, 2, 0, 0]);
723+
724+
// Test dtype mismatch error.
725+
let mut builder = ListViewBuilder::<u32, u32>::with_capacity(dtype, NonNullable, 20, 10);
726+
let wrong_dtype_arr = buffer![1i64, 2, 3].into_array();
727+
assert!(builder.append_array_as_list(&wrong_dtype_arr).is_err());
728+
}
636729
}

0 commit comments

Comments
 (0)