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