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