1use std::fmt::Write;
2
3use arrow::array::ValueSize;
4use polars_compute::gather::sublist::list::{index_is_oob, sublist_get};
5use polars_core::chunked_array::builder::get_list_builder;
6#[cfg(feature = "diff")]
7use polars_core::series::ops::NullBehavior;
8use polars_core::utils::{CustomIterTools, try_get_supertype};
9
10use super::*;
11use crate::chunked_array::list::min_max::{list_max_function, list_min_function};
12use crate::chunked_array::list::sum_mean::sum_with_nulls;
13#[cfg(feature = "diff")]
14use crate::prelude::diff;
15use crate::prelude::list::sum_mean::{mean_list_numerical, sum_list_numerical};
16use crate::series::{ArgAgg, convert_and_bound_index};
17
18pub(super) fn has_inner_nulls(ca: &ListChunked) -> bool {
19 for arr in ca.downcast_iter() {
20 if arr.values().null_count() > 0 {
21 return true;
22 }
23 }
24 false
25}
26
27fn cast_rhs(
28 other: &mut [Column],
29 inner_type: &DataType,
30 dtype: &DataType,
31 length: usize,
32 allow_broadcast: bool,
33) -> PolarsResult<()> {
34 for s in other.iter_mut() {
35 if !matches!(s.dtype(), DataType::List(_)) {
37 *s = s.cast(inner_type)?
38 }
39 if !matches!(s.dtype(), DataType::List(_)) && s.dtype() == inner_type {
40 *s = s
42 .reshape_list(&[ReshapeDimension::Infer, ReshapeDimension::new_dimension(1)])
43 .unwrap();
44 }
45 if s.dtype() != dtype {
46 *s = s.cast(dtype).map_err(|e| {
47 polars_err!(
48 SchemaMismatch:
49 "cannot concat `{}` into a list of `{}`: {}",
50 s.dtype(),
51 dtype,
52 e
53 )
54 })?;
55 }
56
57 if s.len() != length {
58 polars_ensure!(
59 s.len() == 1,
60 ShapeMismatch: "series length {} does not match expected length of {}",
61 s.len(), length
62 );
63 if allow_broadcast {
64 *s = s.new_from_index(0, length)
66 }
67 }
69 }
70 Ok(())
71}
72
73pub trait ListNameSpaceImpl: AsList {
74 fn lst_join(
77 &self,
78 separator: &StringChunked,
79 ignore_nulls: bool,
80 ) -> PolarsResult<StringChunked> {
81 let ca = self.as_list();
82 match ca.inner_dtype() {
83 DataType::String => match separator.len() {
84 1 => match separator.get(0) {
85 Some(separator) => self.join_literal(separator, ignore_nulls),
86 _ => Ok(StringChunked::full_null(ca.name().clone(), ca.len())),
87 },
88 _ => self.join_many(separator, ignore_nulls),
89 },
90 dt => polars_bail!(op = "`lst.join`", got = dt, expected = "String"),
91 }
92 }
93
94 fn join_literal(&self, separator: &str, ignore_nulls: bool) -> PolarsResult<StringChunked> {
95 let ca = self.as_list();
96 let mut buf = String::with_capacity(128);
98 let mut builder = StringChunkedBuilder::new(ca.name().clone(), ca.len());
99
100 ca.for_each_amortized(|opt_s| {
101 let opt_val = opt_s.and_then(|s| {
102 buf.clear();
104 let ca = s.as_ref().str().unwrap();
105
106 if ca.null_count() != 0 && !ignore_nulls {
107 return None;
108 }
109
110 for arr in ca.downcast_iter() {
111 for val in arr.non_null_values_iter() {
112 buf.write_str(val).unwrap();
113 buf.write_str(separator).unwrap();
114 }
115 }
116
117 Some(&buf[..buf.len().saturating_sub(separator.len())])
120 });
121 builder.append_option(opt_val)
122 });
123 Ok(builder.finish())
124 }
125
126 fn join_many(
127 &self,
128 separator: &StringChunked,
129 ignore_nulls: bool,
130 ) -> PolarsResult<StringChunked> {
131 let ca = self.as_list();
132 let mut buf = String::with_capacity(128);
134 let mut builder = StringChunkedBuilder::new(ca.name().clone(), ca.len());
135 {
136 ca.amortized_iter()
137 .zip(separator.iter())
138 .for_each(|(opt_s, opt_sep)| match opt_sep {
139 Some(separator) => {
140 let opt_val = opt_s.and_then(|s| {
141 buf.clear();
143 let ca = s.as_ref().str().unwrap();
144
145 if ca.null_count() != 0 && !ignore_nulls {
146 return None;
147 }
148
149 for arr in ca.downcast_iter() {
150 for val in arr.non_null_values_iter() {
151 buf.write_str(val).unwrap();
152 buf.write_str(separator).unwrap();
153 }
154 }
155
156 Some(&buf[..buf.len().saturating_sub(separator.len())])
159 });
160 builder.append_option(opt_val)
161 },
162 _ => builder.append_null(),
163 })
164 }
165 Ok(builder.finish())
166 }
167
168 fn lst_max(&self) -> PolarsResult<Series> {
169 list_max_function(self.as_list())
170 }
171
172 fn lst_min(&self) -> PolarsResult<Series> {
173 list_min_function(self.as_list())
174 }
175
176 fn lst_sum(&self) -> PolarsResult<Series> {
177 let ca = self.as_list();
178
179 if has_inner_nulls(ca) {
180 return sum_with_nulls(ca, ca.inner_dtype());
181 };
182
183 match ca.inner_dtype() {
184 DataType::Boolean => Ok(count_boolean_bits(ca).into_series()),
185 dt if dt.is_primitive_numeric() => Ok(sum_list_numerical(ca, dt)),
186 dt => sum_with_nulls(ca, dt),
187 }
188 }
189
190 fn lst_mean(&self) -> Series {
191 let ca = self.as_list();
192
193 if has_inner_nulls(ca) {
194 return sum_mean::mean_with_nulls(ca);
195 };
196
197 match ca.inner_dtype() {
198 dt if dt.is_primitive_numeric() => mean_list_numerical(ca, dt),
199 _ => sum_mean::mean_with_nulls(ca),
200 }
201 }
202
203 fn lst_median(&self) -> Series {
204 let ca = self.as_list();
205 dispersion::median_with_nulls(ca)
206 }
207
208 fn lst_std(&self, ddof: u8) -> Series {
209 let ca = self.as_list();
210 dispersion::std_with_nulls(ca, ddof)
211 }
212
213 fn lst_var(&self, ddof: u8) -> PolarsResult<Series> {
214 let ca = self.as_list();
215 dispersion::var_with_nulls(ca, ddof)
216 }
217
218 fn same_type(&self, out: ListChunked) -> ListChunked {
219 let ca = self.as_list();
220 let dtype = ca.dtype();
221 if out.dtype() != dtype {
222 out.cast(ca.dtype()).unwrap().list().unwrap().clone()
223 } else {
224 out
225 }
226 }
227
228 fn lst_sort(&self, options: SortOptions) -> PolarsResult<ListChunked> {
229 let ca = self.as_list();
230 let out = unsafe { ca.try_apply_amortized_same_type(|s| s.as_ref().sort_with(options))? };
232 Ok(self.same_type(out))
233 }
234
235 fn lst_arg_min(&self) -> IdxCa {
236 let ca = self.as_list();
237 ca.apply_amortized_generic(|opt_s| {
238 opt_s.and_then(|s| s.as_ref().arg_min().map(|idx| idx as IdxSize))
239 })
240 }
241
242 fn lst_arg_max(&self) -> IdxCa {
243 let ca = self.as_list();
244 ca.apply_amortized_generic(|opt_s| {
245 opt_s.and_then(|s| s.as_ref().arg_max().map(|idx| idx as IdxSize))
246 })
247 }
248
249 #[cfg(feature = "diff")]
250 fn lst_diff(&self, n: i64, null_behavior: NullBehavior) -> PolarsResult<ListChunked> {
251 let ca = self.as_list();
252 ca.try_apply_amortized(|s| diff(s.as_ref(), n, null_behavior))
253 }
254
255 fn lst_shift(&self, periods: &Column) -> PolarsResult<ListChunked> {
256 let ca = self.as_list();
257 let periods_s = periods.cast(&DataType::Int64)?;
258 let periods = periods_s.i64()?;
259
260 polars_ensure!(
261 ca.len() == periods.len() || ca.len() == 1 || periods.len() == 1,
262 length_mismatch = "list.shift",
263 ca.len(),
264 periods.len()
265 );
266
267 let target_len = periods.len();
268 if ca.len() == 1 && target_len > 1 {
269 let single_list = ca.get_as_series(0);
270 let out = shift_broadcast_list(
271 single_list,
272 periods,
273 target_len,
274 ca.name().clone(),
275 ca.inner_dtype(),
276 );
277 return Ok(self.same_type(out));
278 }
279
280 let out = match periods.len() {
281 1 => {
282 if let Some(periods) = periods.get(0) {
283 unsafe { ca.apply_amortized_same_type(|s| s.as_ref().shift(periods)) }
285 } else {
286 ListChunked::full_null_with_dtype(ca.name().clone(), ca.len(), ca.inner_dtype())
287 }
288 },
289 _ => ca.zip_and_apply_amortized(periods, |opt_s, opt_periods| {
290 match (opt_s, opt_periods) {
291 (Some(s), Some(periods)) => Some(s.as_ref().shift(periods)),
292 _ => None,
293 }
294 }),
295 };
296 Ok(self.same_type(out))
297 }
298
299 fn lst_slice(&self, offset: i64, length: usize) -> ListChunked {
300 let ca = self.as_list();
301 unsafe { ca.apply_amortized_same_type(|s| s.as_ref().slice(offset, length)) }
303 }
304
305 fn lst_lengths(&self) -> IdxCa {
306 let ca = self.as_list();
307
308 let ca_validity = ca.rechunk_validity();
309
310 if ca_validity.as_ref().is_some_and(|x| x.set_bits() == 0) {
311 return IdxCa::full_null(ca.name().clone(), ca.len());
312 }
313
314 let mut lengths = Vec::with_capacity(ca.len());
315 ca.downcast_iter().for_each(|arr| {
316 let offsets = arr.offsets().as_slice();
317 let mut last = offsets[0];
318 for o in &offsets[1..] {
319 lengths.push((*o - last) as IdxSize);
320 last = *o;
321 }
322 });
323
324 let arr = IdxArr::from_vec(lengths).with_validity(ca_validity);
325 IdxCa::with_chunk(ca.name().clone(), arr)
326 }
327
328 fn lst_get(&self, idx: i64, null_on_oob: bool) -> PolarsResult<Series> {
333 let ca = self.as_list();
334 if !null_on_oob && ca.downcast_iter().any(|arr| index_is_oob(arr, idx)) {
335 polars_bail!(ComputeError: "get index is out of bounds");
336 }
337
338 let chunks = ca
339 .downcast_iter()
340 .map(|arr| sublist_get(arr, idx))
341 .collect::<Vec<_>>();
342
343 let s = Series::try_from((ca.name().clone(), chunks)).unwrap();
344 unsafe { s.from_physical_unchecked(ca.inner_dtype()) }
346 }
347
348 #[cfg(feature = "list_gather")]
349 fn lst_gather_every(&self, n: &IdxCa, offset: &IdxCa) -> PolarsResult<Series> {
350 let list_ca = self.as_list();
351 let out = match (n.len(), offset.len()) {
352 (1, 1) => match (n.get(0), offset.get(0)) {
353 (Some(n), Some(offset)) => unsafe {
354 list_ca.try_apply_amortized_same_type(|s| {
356 s.as_ref().gather_every(n as usize, offset as usize)
357 })?
358 },
359 _ => ListChunked::full_null_with_dtype(
360 list_ca.name().clone(),
361 list_ca.len(),
362 list_ca.inner_dtype(),
363 ),
364 },
365 (1, len_offset) if len_offset == list_ca.len() => {
366 if let Some(n) = n.get(0) {
367 list_ca.try_zip_and_apply_amortized(offset, |opt_s, opt_offset| {
368 match (opt_s, opt_offset) {
369 (Some(s), Some(offset)) => {
370 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
371 },
372 _ => Ok(None),
373 }
374 })?
375 } else {
376 ListChunked::full_null_with_dtype(
377 list_ca.name().clone(),
378 list_ca.len(),
379 list_ca.inner_dtype(),
380 )
381 }
382 },
383 (len_n, 1) if len_n == list_ca.len() => {
384 if let Some(offset) = offset.get(0) {
385 list_ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
386 (Some(s), Some(n)) => {
387 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
388 },
389 _ => Ok(None),
390 })?
391 } else {
392 ListChunked::full_null_with_dtype(
393 list_ca.name().clone(),
394 list_ca.len(),
395 list_ca.inner_dtype(),
396 )
397 }
398 },
399 (len_n, len_offset) if len_n == len_offset && len_n == list_ca.len() => list_ca
400 .try_binary_zip_and_apply_amortized(
401 n,
402 offset,
403 |opt_s, opt_n, opt_offset| match (opt_s, opt_n, opt_offset) {
404 (Some(s), Some(n), Some(offset)) => {
405 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
406 },
407 _ => Ok(None),
408 },
409 )?,
410 _ => {
411 polars_bail!(ComputeError: "The lengths of `n` and `offset` should be 1 or equal to the length of list.")
412 },
413 };
414 Ok(out.into_series())
415 }
416
417 #[cfg(feature = "list_gather")]
418 fn lst_gather(&self, idx: &Series, null_on_oob: bool) -> PolarsResult<Series> {
419 let list_ca = self.as_list();
420 let idx_ca = idx.list()?;
421
422 polars_ensure!(
423 idx_ca.inner_dtype().is_integer(),
424 ComputeError: "cannot use dtype `{}` as an index", idx_ca.inner_dtype()
425 );
426
427 let index_typed_index = |idx: &Series| {
428 let idx = idx.cast(&IDX_DTYPE).unwrap();
429 {
430 list_ca
431 .amortized_iter()
432 .map(|s| {
433 s.map(|s| {
434 let s = s.as_ref();
435 take_series(s, idx.clone(), null_on_oob)
436 })
437 .transpose()
438 })
439 .collect::<PolarsResult<ListChunked>>()
440 .map(|mut ca| {
441 ca.rename(list_ca.name().clone());
442 ca.into_series()
443 })
444 }
445 };
446
447 match (list_ca.len(), idx_ca.len()) {
448 (1, _) => {
449 let mut out = if list_ca.has_nulls() {
450 ListChunked::full_null_with_dtype(
451 PlSmallStr::EMPTY,
452 idx.len(),
453 list_ca.inner_dtype(),
454 )
455 } else {
456 let s = list_ca.explode(ExplodeOptions {
457 empty_as_null: true,
458 keep_nulls: true,
459 })?;
460 idx_ca
461 .series_iter()
462 .map(|opt_idx| {
463 opt_idx
464 .map(|idx| take_series(&s, idx, null_on_oob))
465 .transpose()
466 })
467 .collect::<PolarsResult<ListChunked>>()?
468 };
469 out.rename(list_ca.name().clone());
470 Ok(out.into_series())
471 },
472 (_, 1) => {
473 let idx_ca = idx_ca.explode(ExplodeOptions {
474 empty_as_null: true,
475 keep_nulls: true,
476 })?;
477
478 use DataType as D;
479 match idx_ca.dtype() {
480 D::UInt32 | D::UInt64 => index_typed_index(&idx_ca),
481 dt if dt.is_signed_integer() => {
482 if let Some(min) = idx_ca.min::<i64>().unwrap() {
483 if min >= 0 {
484 index_typed_index(&idx_ca)
485 } else {
486 let mut out = {
487 list_ca
488 .amortized_iter()
489 .map(|opt_s| {
490 opt_s
491 .map(|s| {
492 take_series(
493 s.as_ref(),
494 idx_ca.clone(),
495 null_on_oob,
496 )
497 })
498 .transpose()
499 })
500 .collect::<PolarsResult<ListChunked>>()?
501 };
502 out.rename(list_ca.name().clone());
503 Ok(out.into_series())
504 }
505 } else {
506 polars_bail!(ComputeError: "all indices are null");
507 }
508 },
509 dt => polars_bail!(ComputeError: "cannot use dtype `{dt}` as an index"),
510 }
511 },
512 (a, b) if a == b => {
513 let mut out = {
514 list_ca
515 .amortized_iter()
516 .zip(idx_ca.series_iter())
517 .map(|(opt_s, opt_idx)| {
518 {
519 match (opt_s, opt_idx) {
520 (Some(s), Some(idx)) => {
521 Some(take_series(s.as_ref(), idx, null_on_oob))
522 },
523 _ => None,
524 }
525 }
526 .transpose()
527 })
528 .collect::<PolarsResult<ListChunked>>()?
529 };
530 out.rename(list_ca.name().clone());
531 Ok(out.into_series())
532 },
533 (a, b) => polars_bail!(length_mismatch = "list.gather", a, b),
534 }
535 }
536
537 #[cfg(feature = "list_drop_nulls")]
538 fn lst_drop_nulls(&self) -> ListChunked {
539 let list_ca = self.as_list();
540
541 unsafe { list_ca.apply_amortized_same_type(|s| s.as_ref().drop_nulls()) }
543 }
544
545 #[cfg(feature = "list_sample")]
546 fn lst_sample_n(
547 &self,
548 n: &Series,
549 with_replacement: bool,
550 shuffle: bool,
551 seed: Option<u64>,
552 ) -> PolarsResult<ListChunked> {
553 use std::borrow::Cow;
554
555 let ca = self.as_list();
556
557 let n_s = n.strict_cast(&IDX_DTYPE)?;
558 let n = n_s.idx()?;
559
560 polars_ensure!(
561 ca.len() == n.len() || ca.len() == 1 || n.len() == 1,
562 length_mismatch = "list.sample(n)",
563 ca.len(),
564 n.len()
565 );
566
567 let mut ca = Cow::Borrowed(ca);
569 if ca.len() == 1 && n.len() != 1 {
570 ca = Cow::Owned(ca.new_from_index(0, n.len()));
572 }
573 let ca = ca.as_ref();
574
575 let out = match n.len() {
576 1 => {
577 if let Some(n) = n.get(0) {
578 unsafe {
579 ca.try_apply_amortized_same_type(|s| {
581 s.as_ref()
582 .sample_n(n as usize, with_replacement, shuffle, seed)
583 })
584 }
585 } else {
586 Ok(ListChunked::full_null_with_dtype(
587 ca.name().clone(),
588 ca.len(),
589 ca.inner_dtype(),
590 ))
591 }
592 },
593 _ => ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
594 (Some(s), Some(n)) => s
595 .as_ref()
596 .sample_n(n as usize, with_replacement, shuffle, seed)
597 .map(Some),
598 _ => Ok(None),
599 }),
600 };
601 out.map(|ok| self.same_type(ok))
602 }
603
604 #[cfg(feature = "list_sample")]
605 fn lst_sample_fraction(
606 &self,
607 fraction: &Series,
608 with_replacement: bool,
609 shuffle: bool,
610 seed: Option<u64>,
611 ) -> PolarsResult<ListChunked> {
612 use std::borrow::Cow;
613
614 let ca = self.as_list();
615
616 let fraction_s = fraction.cast(&DataType::Float64)?;
617 let fraction = fraction_s.f64()?;
618
619 if !with_replacement {
620 for frac in fraction.iter().flatten() {
621 polars_ensure!(
622 (0.0..=1.0).contains(&frac),
623 ComputeError: "fraction must be between 0.0 and 1.0, got: {}", frac
624 )
625 }
626 }
627
628 polars_ensure!(
629 ca.len() == fraction.len() || ca.len() == 1 || fraction.len() == 1,
630 length_mismatch = "list.sample(fraction)",
631 ca.len(),
632 fraction.len()
633 );
634
635 let mut ca = Cow::Borrowed(ca);
637 if ca.len() == 1 && fraction.len() != 1 {
638 ca = Cow::Owned(ca.new_from_index(0, fraction.len()));
640 }
641 let ca = ca.as_ref();
642
643 let out = match fraction.len() {
644 1 => {
645 if let Some(fraction) = fraction.get(0) {
646 unsafe {
647 ca.try_apply_amortized_same_type(|s| {
649 let n = (s.as_ref().len() as f64 * fraction) as usize;
650 s.as_ref().sample_n(n, with_replacement, shuffle, seed)
651 })
652 }
653 } else {
654 Ok(ListChunked::full_null_with_dtype(
655 ca.name().clone(),
656 ca.len(),
657 ca.inner_dtype(),
658 ))
659 }
660 },
661 _ => ca.try_zip_and_apply_amortized(fraction, |opt_s, opt_n| match (opt_s, opt_n) {
662 (Some(s), Some(fraction)) => {
663 let n = (s.as_ref().len() as f64 * fraction) as usize;
664 s.as_ref()
665 .sample_n(n, with_replacement, shuffle, seed)
666 .map(Some)
667 },
668 _ => Ok(None),
669 }),
670 };
671 out.map(|ok| self.same_type(ok))
672 }
673
674 fn lst_concat(&self, other: &[Column]) -> PolarsResult<ListChunked> {
675 let ca = self.as_list();
676 let other_len = other.len();
677 let length = ca.len();
678 let mut other = other.to_vec();
679 let mut inner_super_type = ca.inner_dtype().clone();
680
681 for s in &other {
682 match s.dtype() {
683 DataType::List(inner_type) => {
684 inner_super_type = try_get_supertype(&inner_super_type, inner_type)?;
685 },
686 dt => {
687 inner_super_type = try_get_supertype(&inner_super_type, dt)?;
688 },
689 }
690 }
691
692 let dtype = &DataType::List(Box::new(inner_super_type.clone()));
694 let ca = ca.cast(dtype)?;
695 let ca = ca.list().unwrap();
696
697 let out = if other.iter().all(|s| s.len() == 1) && ca.len() != 1 {
700 cast_rhs(&mut other, &inner_super_type, dtype, length, false)?;
701 let to_append = other
702 .iter()
703 .filter_map(|s| {
704 let lst = s.list().unwrap();
705 unsafe {
707 lst.get_as_series(0)
708 .map(|s| s.from_physical_unchecked(&inner_super_type).unwrap())
709 }
710 })
711 .collect::<Vec<_>>();
712
713 if to_append.len() != other_len {
715 return Ok(ListChunked::full_null_with_dtype(
716 ca.name().clone(),
717 length,
718 &inner_super_type,
719 ));
720 }
721
722 let vals_size_other = other
723 .iter()
724 .map(|s| s.list().unwrap().get_values_size())
725 .sum::<usize>();
726
727 let mut builder = get_list_builder(
728 &inner_super_type,
729 ca.get_values_size() + vals_size_other + 1,
730 length,
731 ca.name().clone(),
732 );
733 ca.series_iter().for_each(|opt_s| {
734 let opt_s = opt_s.map(|mut s| {
735 for append in &to_append {
736 s.append(append).unwrap();
737 }
738 match inner_super_type {
739 #[cfg(feature = "dtype-struct")]
741 DataType::Struct(_) => s = s.rechunk(),
742 _ => {},
744 }
745 s
746 });
747 builder.append_opt_series(opt_s.as_ref()).unwrap();
748 });
749 builder.finish()
750 } else {
751 cast_rhs(&mut other, &inner_super_type, dtype, length, true)?;
753
754 let vals_size_other = other
755 .iter()
756 .map(|s| s.list().unwrap().get_values_size())
757 .sum::<usize>();
758 let mut iters = Vec::with_capacity(other_len + 1);
759
760 for s in other.iter_mut() {
761 iters.push(s.list()?.amortized_iter())
762 }
763 let mut first_iter = ca.series_iter();
764 let mut builder = get_list_builder(
765 &inner_super_type,
766 ca.get_values_size() + vals_size_other + 1,
767 length,
768 ca.name().clone(),
769 );
770
771 for _ in 0..ca.len() {
772 let mut acc = match first_iter.next().unwrap() {
773 Some(s) => s,
774 None => {
775 builder.append_null();
776 for it in &mut iters {
778 it.next().unwrap();
779 }
780 continue;
781 },
782 };
783
784 let mut has_nulls = false;
785 for it in &mut iters {
786 match it.next().unwrap() {
787 Some(s) => {
788 if !has_nulls {
789 acc.append(s.as_ref())?;
790 }
791 },
792 None => {
793 has_nulls = true;
794 },
795 }
796 }
797 if has_nulls {
798 builder.append_null();
799 continue;
800 }
801
802 match inner_super_type {
803 #[cfg(feature = "dtype-struct")]
805 DataType::Struct(_) => acc = acc.rechunk(),
806 _ => {},
808 }
809 builder.append_series(&acc).unwrap();
810 }
811 builder.finish()
812 };
813 Ok(out)
814 }
815}
816
817impl ListNameSpaceImpl for ListChunked {}
818
819#[cfg(feature = "list_gather")]
820fn take_series(s: &Series, idx: Series, null_on_oob: bool) -> PolarsResult<Series> {
821 let len = s.len();
822 let idx = convert_and_bound_index(&idx, len, null_on_oob)?;
823 s.take(&idx)
824}
825
826pub fn slice_broadcast_list(
827 single_list: Option<Series>,
828 offsets: &Int64Chunked,
829 lengths: &Int64Chunked,
830 target_len: usize,
831 name: PlSmallStr,
832 inner_dtype: &DataType,
833) -> ListChunked {
834 debug_assert!(target_len == offsets.len().max(lengths.len()));
835
836 let Some(single_list) = single_list else {
837 return ListChunked::full_null_with_dtype(name, target_len, inner_dtype);
838 };
839
840 let iter = (0..target_len).map(|index| {
841 let opt_offset = offsets.get(if offsets.len() == 1 { 0 } else { index });
842 let opt_length = lengths.get(if lengths.len() == 1 { 0 } else { index });
843 match (opt_offset, opt_length) {
844 (Some(offset), Some(length)) => Some(single_list.slice(offset, length as usize)),
845 _ => None,
846 }
847 });
848
849 let mut out: ListChunked = iter.collect_trusted();
850 out.rename(name);
851 out
852}
853
854fn shift_broadcast_list(
855 single_list: Option<Series>,
856 periods: &Int64Chunked,
857 target_len: usize,
858 name: PlSmallStr,
859 inner_dtype: &DataType,
860) -> ListChunked {
861 debug_assert!(target_len == periods.len());
862
863 let Some(single_list) = single_list else {
864 return ListChunked::full_null_with_dtype(name, target_len, inner_dtype);
865 };
866
867 let iter = (0..target_len).map(|index| {
868 let opt_period = periods.get(index);
869 opt_period.map(|period| single_list.shift(period))
870 });
871
872 let mut out: ListChunked = iter.collect_trusted();
873 out.rename(name);
874 out
875}