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