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_lengths(&self) -> IdxCa {
298 let ca = self.as_list();
299
300 let ca_validity = ca.rechunk_validity();
301
302 if ca_validity.as_ref().is_some_and(|x| x.set_bits() == 0) {
303 return IdxCa::full_null(ca.name().clone(), ca.len());
304 }
305
306 let mut lengths = Vec::with_capacity(ca.len());
307 ca.downcast_iter().for_each(|arr| {
308 let offsets = arr.offsets().as_slice();
309 let mut last = offsets[0];
310 for o in &offsets[1..] {
311 lengths.push((*o - last) as IdxSize);
312 last = *o;
313 }
314 });
315
316 let arr = IdxArr::from_vec(lengths).with_validity(ca_validity);
317 IdxCa::with_chunk(ca.name().clone(), arr)
318 }
319
320 fn lst_get(&self, idx: i64, null_on_oob: bool) -> PolarsResult<Series> {
325 let ca = self.as_list();
326 if !null_on_oob && ca.downcast_iter().any(|arr| index_is_oob(arr, idx)) {
327 polars_bail!(ComputeError: "get index is out of bounds");
328 }
329
330 let chunks = ca
331 .downcast_iter()
332 .map(|arr| sublist_get(arr, idx))
333 .collect::<Vec<_>>();
334
335 let s = Series::try_from((ca.name().clone(), chunks)).unwrap();
336 unsafe { s.from_physical_unchecked(ca.inner_dtype()) }
338 }
339
340 #[cfg(feature = "list_gather")]
341 fn lst_gather_every(&self, n: &IdxCa, offset: &IdxCa) -> PolarsResult<Series> {
342 let list_ca = self.as_list();
343 let out = match (n.len(), offset.len()) {
344 (1, 1) => match (n.get(0), offset.get(0)) {
345 (Some(n), Some(offset)) => unsafe {
346 list_ca.try_apply_amortized_same_type(|s| {
348 s.as_ref().gather_every(n as usize, offset as usize)
349 })?
350 },
351 _ => ListChunked::full_null_with_dtype(
352 list_ca.name().clone(),
353 list_ca.len(),
354 list_ca.inner_dtype(),
355 ),
356 },
357 (1, len_offset) if len_offset == list_ca.len() => {
358 if let Some(n) = n.get(0) {
359 list_ca.try_zip_and_apply_amortized(offset, |opt_s, opt_offset| {
360 match (opt_s, opt_offset) {
361 (Some(s), Some(offset)) => {
362 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
363 },
364 _ => Ok(None),
365 }
366 })?
367 } else {
368 ListChunked::full_null_with_dtype(
369 list_ca.name().clone(),
370 list_ca.len(),
371 list_ca.inner_dtype(),
372 )
373 }
374 },
375 (len_n, 1) if len_n == list_ca.len() => {
376 if let Some(offset) = offset.get(0) {
377 list_ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
378 (Some(s), Some(n)) => {
379 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
380 },
381 _ => Ok(None),
382 })?
383 } else {
384 ListChunked::full_null_with_dtype(
385 list_ca.name().clone(),
386 list_ca.len(),
387 list_ca.inner_dtype(),
388 )
389 }
390 },
391 (len_n, len_offset) if len_n == len_offset && len_n == list_ca.len() => list_ca
392 .try_binary_zip_and_apply_amortized(
393 n,
394 offset,
395 |opt_s, opt_n, opt_offset| match (opt_s, opt_n, opt_offset) {
396 (Some(s), Some(n), Some(offset)) => {
397 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
398 },
399 _ => Ok(None),
400 },
401 )?,
402 _ => {
403 polars_bail!(ComputeError: "The lengths of `n` and `offset` should be 1 or equal to the length of list.")
404 },
405 };
406 Ok(out.into_series())
407 }
408
409 #[cfg(feature = "list_gather")]
410 fn lst_gather(&self, idx: &Series, null_on_oob: bool) -> PolarsResult<Series> {
411 let list_ca = self.as_list();
412 let idx_ca = idx.list()?;
413
414 polars_ensure!(
415 idx_ca.inner_dtype().is_integer(),
416 ComputeError: "cannot use dtype `{}` as an index", idx_ca.inner_dtype()
417 );
418
419 let index_typed_index = |idx: &Series| {
420 let idx = idx.cast(&IDX_DTYPE).unwrap();
421 {
422 list_ca
423 .amortized_iter()
424 .map(|s| {
425 s.map(|s| {
426 let s = s.as_ref();
427 take_series(s, idx.clone(), null_on_oob)
428 })
429 .transpose()
430 })
431 .collect::<PolarsResult<ListChunked>>()
432 .map(|mut ca| {
433 ca.rename(list_ca.name().clone());
434 ca.into_series()
435 })
436 }
437 };
438
439 match (list_ca.len(), idx_ca.len()) {
440 (1, _) => {
441 let mut out = if list_ca.has_nulls() {
442 ListChunked::full_null_with_dtype(
443 PlSmallStr::EMPTY,
444 idx.len(),
445 list_ca.inner_dtype(),
446 )
447 } else {
448 let s = list_ca.explode(ExplodeOptions {
449 empty_as_null: true,
450 keep_nulls: true,
451 })?;
452 idx_ca
453 .series_iter()
454 .map(|opt_idx| {
455 opt_idx
456 .map(|idx| take_series(&s, idx, null_on_oob))
457 .transpose()
458 })
459 .collect::<PolarsResult<ListChunked>>()?
460 };
461 out.rename(list_ca.name().clone());
462 Ok(out.into_series())
463 },
464 (_, 1) => {
465 let idx_ca = idx_ca.explode(ExplodeOptions {
466 empty_as_null: true,
467 keep_nulls: true,
468 })?;
469
470 use DataType as D;
471 match idx_ca.dtype() {
472 D::UInt32 | D::UInt64 => index_typed_index(&idx_ca),
473 dt if dt.is_signed_integer() => {
474 if let Some(min) = idx_ca.min::<i64>().unwrap() {
475 if min >= 0 {
476 index_typed_index(&idx_ca)
477 } else {
478 let mut out = {
479 list_ca
480 .amortized_iter()
481 .map(|opt_s| {
482 opt_s
483 .map(|s| {
484 take_series(
485 s.as_ref(),
486 idx_ca.clone(),
487 null_on_oob,
488 )
489 })
490 .transpose()
491 })
492 .collect::<PolarsResult<ListChunked>>()?
493 };
494 out.rename(list_ca.name().clone());
495 Ok(out.into_series())
496 }
497 } else {
498 polars_bail!(ComputeError: "all indices are null");
499 }
500 },
501 dt => polars_bail!(ComputeError: "cannot use dtype `{dt}` as an index"),
502 }
503 },
504 (a, b) if a == b => {
505 let mut out = {
506 list_ca
507 .amortized_iter()
508 .zip(idx_ca.series_iter())
509 .map(|(opt_s, opt_idx)| {
510 {
511 match (opt_s, opt_idx) {
512 (Some(s), Some(idx)) => {
513 Some(take_series(s.as_ref(), idx, null_on_oob))
514 },
515 _ => None,
516 }
517 }
518 .transpose()
519 })
520 .collect::<PolarsResult<ListChunked>>()?
521 };
522 out.rename(list_ca.name().clone());
523 Ok(out.into_series())
524 },
525 (a, b) => polars_bail!(length_mismatch = "list.gather", a, b),
526 }
527 }
528
529 #[cfg(feature = "list_drop_nulls")]
530 fn lst_drop_nulls(&self) -> ListChunked {
531 let list_ca = self.as_list();
532
533 unsafe { list_ca.apply_amortized_same_type(|s| s.as_ref().drop_nulls()) }
535 }
536
537 #[cfg(feature = "list_sample")]
538 fn lst_sample_n(
539 &self,
540 n: &Series,
541 with_replacement: bool,
542 shuffle: Option<bool>,
543 seed: Option<u64>,
544 ) -> PolarsResult<ListChunked> {
545 let ca = self.as_list();
546
547 let n_s = n.strict_cast(&IDX_DTYPE)?;
548 let n = n_s.idx()?;
549
550 polars_ensure!(
551 ca.len() == n.len() || ca.len() == 1 || n.len() == 1,
552 length_mismatch = "list.sample(n)",
553 ca.len(),
554 n.len()
555 );
556
557 let target_len = n.len();
558 if ca.len() == 1 && target_len > 1 {
559 let single_list = ca.get_as_series(0);
560 let out = sample_n_broadcast_list(
561 single_list,
562 n,
563 with_replacement,
564 shuffle,
565 seed,
566 target_len,
567 ca.name().clone(),
568 ca.inner_dtype(),
569 )?;
570 return Ok(self.same_type(out));
571 }
572
573 let out = match n.len() {
574 1 => {
575 if let Some(n) = n.get(0) {
576 unsafe {
577 ca.try_apply_amortized_same_type(|s| {
579 s.as_ref()
580 .sample_n(n as usize, with_replacement, shuffle, seed)
581 })
582 }
583 } else {
584 Ok(ListChunked::full_null_with_dtype(
585 ca.name().clone(),
586 ca.len(),
587 ca.inner_dtype(),
588 ))
589 }
590 },
591 _ => ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
592 (Some(s), Some(n)) => s
593 .as_ref()
594 .sample_n(n as usize, with_replacement, shuffle, seed)
595 .map(Some),
596 _ => Ok(None),
597 }),
598 };
599 out.map(|ok| self.same_type(ok))
600 }
601
602 #[cfg(feature = "list_sample")]
603 fn lst_sample_fraction(
604 &self,
605 fraction: &Series,
606 with_replacement: bool,
607 shuffle: Option<bool>,
608 seed: Option<u64>,
609 ) -> PolarsResult<ListChunked> {
610 let ca = self.as_list();
611
612 let fraction_s = fraction.cast(&DataType::Float64)?;
613 let fraction = fraction_s.f64()?;
614
615 if !with_replacement {
616 for frac in fraction.iter().flatten() {
617 polars_ensure!(
618 (0.0..=1.0).contains(&frac),
619 ComputeError: "fraction must be between 0.0 and 1.0, got: {}", frac
620 )
621 }
622 }
623
624 polars_ensure!(
625 ca.len() == fraction.len() || ca.len() == 1 || fraction.len() == 1,
626 length_mismatch = "list.sample(fraction)",
627 ca.len(),
628 fraction.len()
629 );
630
631 let target_len = fraction.len();
632 if ca.len() == 1 && target_len > 1 {
633 let single_list = ca.get_as_series(0);
634 let out = sample_frac_broadcast_list(
635 single_list,
636 fraction,
637 with_replacement,
638 shuffle,
639 seed,
640 target_len,
641 ca.name().clone(),
642 ca.inner_dtype(),
643 )?;
644 return Ok(self.same_type(out));
645 }
646
647 let out = match fraction.len() {
648 1 => {
649 if let Some(fraction) = fraction.get(0) {
650 unsafe {
651 ca.try_apply_amortized_same_type(|s| {
653 let n = (s.as_ref().len() as f64 * fraction) as usize;
654 s.as_ref().sample_n(n, with_replacement, shuffle, seed)
655 })
656 }
657 } else {
658 Ok(ListChunked::full_null_with_dtype(
659 ca.name().clone(),
660 ca.len(),
661 ca.inner_dtype(),
662 ))
663 }
664 },
665 _ => ca.try_zip_and_apply_amortized(fraction, |opt_s, opt_n| match (opt_s, opt_n) {
666 (Some(s), Some(fraction)) => {
667 let n = (s.as_ref().len() as f64 * fraction) as usize;
668 s.as_ref()
669 .sample_n(n, with_replacement, shuffle, seed)
670 .map(Some)
671 },
672 _ => Ok(None),
673 }),
674 };
675 out.map(|ok| self.same_type(ok))
676 }
677
678 fn lst_concat(&self, other: &[Column]) -> PolarsResult<ListChunked> {
679 let ca = self.as_list();
680 if other.is_empty() {
681 return Ok(ca.clone());
682 }
683
684 let other_len = other.len();
685 let length = ca.len();
686 let mut other = other.to_vec();
687 let mut inner_super_type = ca.inner_dtype().clone();
688
689 for s in &other {
690 match s.dtype() {
691 DataType::List(inner_type) => {
692 inner_super_type = try_get_supertype(&inner_super_type, inner_type)?;
693 },
694 dt => {
695 inner_super_type = try_get_supertype(&inner_super_type, dt)?;
696 },
697 }
698 }
699
700 let dtype = &DataType::List(Box::new(inner_super_type.clone()));
702 let ca = ca.cast(dtype)?;
703 let ca = ca.list().unwrap();
704
705 let out = if other.iter().all(|s| s.len() == 1) && ca.len() != 1 {
708 cast_rhs(&mut other, &inner_super_type, dtype, length, false)?;
709 let to_append = other
710 .iter()
711 .filter_map(|s| {
712 let lst = s.list().unwrap();
713 unsafe {
715 lst.get_as_series(0)
716 .map(|s| s.from_physical_unchecked(&inner_super_type).unwrap())
717 }
718 })
719 .collect::<Vec<_>>();
720
721 if to_append.len() != other_len {
723 return Ok(ListChunked::full_null_with_dtype(
724 ca.name().clone(),
725 length,
726 &inner_super_type,
727 ));
728 }
729
730 let vals_size_other = other
731 .iter()
732 .map(|s| s.list().unwrap().get_values_size())
733 .sum::<usize>();
734
735 let mut builder = get_list_builder(
736 &inner_super_type,
737 ca.get_values_size() + vals_size_other + 1,
738 length,
739 ca.name().clone(),
740 );
741 ca.series_iter().for_each(|opt_s| {
742 let opt_s = opt_s.map(|mut s| {
743 for append in &to_append {
744 s.append(append).unwrap();
745 }
746 match inner_super_type {
747 #[cfg(feature = "dtype-struct")]
749 DataType::Struct(_) => s = s.rechunk(),
750 _ => {},
752 }
753 s
754 });
755 builder.append_opt_series(opt_s.as_ref()).unwrap();
756 });
757 builder.finish()
758 } else {
759 cast_rhs(&mut other, &inner_super_type, dtype, length, true)?;
761
762 let vals_size_other = other
763 .iter()
764 .map(|s| s.list().unwrap().get_values_size())
765 .sum::<usize>();
766 let mut iters = Vec::with_capacity(other_len + 1);
767
768 for s in other.iter_mut() {
769 iters.push(s.list()?.amortized_iter())
770 }
771 let mut first_iter = ca.series_iter();
772 let mut builder = get_list_builder(
773 &inner_super_type,
774 ca.get_values_size() + vals_size_other + 1,
775 length,
776 ca.name().clone(),
777 );
778
779 for _ in 0..ca.len() {
780 let mut acc = match first_iter.next().unwrap() {
781 Some(s) => s,
782 None => {
783 builder.append_null();
784 for it in &mut iters {
786 it.next().unwrap();
787 }
788 continue;
789 },
790 };
791
792 let mut has_nulls = false;
793 for it in &mut iters {
794 match it.next().unwrap() {
795 Some(s) => {
796 if !has_nulls {
797 acc.append(s.as_ref())?;
798 }
799 },
800 None => {
801 has_nulls = true;
802 },
803 }
804 }
805 if has_nulls {
806 builder.append_null();
807 continue;
808 }
809
810 match inner_super_type {
811 #[cfg(feature = "dtype-struct")]
813 DataType::Struct(_) => acc = acc.rechunk(),
814 _ => {},
816 }
817 builder.append_series(&acc).unwrap();
818 }
819 builder.finish()
820 };
821 Ok(out)
822 }
823}
824
825impl ListNameSpaceImpl for ListChunked {}
826
827#[cfg(feature = "list_gather")]
828fn take_series(s: &Series, idx: Series, null_on_oob: bool) -> PolarsResult<Series> {
829 let len = s.len();
830 let idx = convert_and_bound_index(&idx, len, null_on_oob)?;
831 s.take(&idx)
832}
833
834pub fn slice_broadcast_list(
835 single_list: Option<Series>,
836 offsets: &Int64Chunked,
837 lengths: &Int64Chunked,
838 target_len: usize,
839 name: PlSmallStr,
840 inner_dtype: &DataType,
841) -> ListChunked {
842 debug_assert!(target_len == offsets.len().max(lengths.len()));
843
844 let Some(single_list) = single_list else {
845 return ListChunked::full_null_with_dtype(name, target_len, inner_dtype);
846 };
847
848 let iter = (0..target_len).map(|index| {
849 let opt_offset = offsets.get(if offsets.len() == 1 { 0 } else { index });
850 let opt_length = lengths.get(if lengths.len() == 1 { 0 } else { index });
851 let offset = opt_offset?;
852 let length = opt_length.map_or(usize::MAX, |l| l as usize);
853 Some(single_list.slice(offset, length))
854 });
855
856 let mut out: ListChunked = iter.collect_trusted();
857 out.rename(name);
858 out
859}
860
861fn shift_broadcast_list(
862 single_list: Option<Series>,
863 periods: &Int64Chunked,
864 target_len: usize,
865 name: PlSmallStr,
866 inner_dtype: &DataType,
867) -> ListChunked {
868 debug_assert!(target_len == periods.len());
869
870 let Some(single_list) = single_list else {
871 return ListChunked::full_null_with_dtype(name, target_len, inner_dtype);
872 };
873
874 let iter = (0..target_len).map(|index| {
875 let opt_period = periods.get(index);
876 opt_period.map(|period| single_list.shift(period))
877 });
878
879 let mut out: ListChunked = iter.collect_trusted();
880 out.rename(name);
881 out
882}
883
884#[cfg(feature = "list_sample")]
885#[allow(clippy::too_many_arguments)]
886fn sample_n_broadcast_list(
887 single_list: Option<Series>,
888 n: &IdxCa,
889 with_replacement: bool,
890 shuffle: Option<bool>,
891 seed: Option<u64>,
892 target_len: usize,
893 name: PlSmallStr,
894 inner_dtype: &DataType,
895) -> PolarsResult<ListChunked> {
896 debug_assert!(target_len == n.len());
897
898 let Some(single_list) = single_list else {
899 return Ok(ListChunked::full_null_with_dtype(
900 name,
901 target_len,
902 inner_dtype,
903 ));
904 };
905
906 let mut out: ListChunked = (0..target_len)
907 .map(|index| -> PolarsResult<Option<Series>> {
908 match n.get(index) {
909 Some(n_val) => single_list
910 .sample_n(n_val as usize, with_replacement, shuffle, seed)
911 .map(Some),
912 None => Ok(None),
913 }
914 })
915 .collect::<PolarsResult<_>>()?;
916
917 out.rename(name);
918 Ok(out)
919}
920
921#[cfg(feature = "list_sample")]
922#[allow(clippy::too_many_arguments)]
923fn sample_frac_broadcast_list(
924 single_list: Option<Series>,
925 fraction: &Float64Chunked,
926 with_replacement: bool,
927 shuffle: Option<bool>,
928 seed: Option<u64>,
929 target_len: usize,
930 name: PlSmallStr,
931 inner_dtype: &DataType,
932) -> PolarsResult<ListChunked> {
933 debug_assert!(target_len == fraction.len());
934
935 let Some(single_list) = single_list else {
936 return Ok(ListChunked::full_null_with_dtype(
937 name,
938 target_len,
939 inner_dtype,
940 ));
941 };
942
943 let mut out: ListChunked = (0..target_len)
944 .map(|index| -> PolarsResult<Option<Series>> {
945 match fraction.get(index) {
946 Some(frac_val) => single_list
947 .sample_frac(frac_val, with_replacement, shuffle, seed)
948 .map(Some),
949 None => Ok(None),
950 }
951 })
952 .collect::<PolarsResult<_>>()?;
953
954 out.rename(name);
955 Ok(out)
956}