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 = ca.try_apply_amortized(|s| s.as_ref().sort_with(options))?;
250 Ok(self.same_type(out))
251 }
252
253 #[must_use]
254 fn lst_reverse(&self) -> ListChunked {
255 let ca = self.as_list();
256 let out = ca.apply_amortized(|s| s.as_ref().reverse());
257 self.same_type(out)
258 }
259
260 fn lst_n_unique(&self) -> PolarsResult<IdxCa> {
261 let ca = self.as_list();
262 ca.try_apply_amortized_generic(|s| {
263 let opt_v = s.map(|s| s.as_ref().n_unique()).transpose()?;
264 Ok(opt_v.map(|idx| idx as IdxSize))
265 })
266 }
267
268 fn lst_unique(&self) -> PolarsResult<ListChunked> {
269 let ca = self.as_list();
270 let out = ca.try_apply_amortized(|s| s.as_ref().unique())?;
271 Ok(self.same_type(out))
272 }
273
274 fn lst_unique_stable(&self) -> PolarsResult<ListChunked> {
275 let ca = self.as_list();
276 let out = ca.try_apply_amortized(|s| s.as_ref().unique_stable())?;
277 Ok(self.same_type(out))
278 }
279
280 fn lst_arg_min(&self) -> IdxCa {
281 let ca = self.as_list();
282 ca.apply_amortized_generic(|opt_s| {
283 opt_s.and_then(|s| s.as_ref().arg_min().map(|idx| idx as IdxSize))
284 })
285 }
286
287 fn lst_arg_max(&self) -> IdxCa {
288 let ca = self.as_list();
289 ca.apply_amortized_generic(|opt_s| {
290 opt_s.and_then(|s| s.as_ref().arg_max().map(|idx| idx as IdxSize))
291 })
292 }
293
294 #[cfg(feature = "diff")]
295 fn lst_diff(&self, n: i64, null_behavior: NullBehavior) -> PolarsResult<ListChunked> {
296 let ca = self.as_list();
297 ca.try_apply_amortized(|s| diff(s.as_ref(), n, null_behavior))
298 }
299
300 fn lst_shift(&self, periods: &Column) -> PolarsResult<ListChunked> {
301 let ca = self.as_list();
302 let periods_s = periods.cast(&DataType::Int64)?;
303 let periods = periods_s.i64()?;
304
305 polars_ensure!(
306 ca.len() == periods.len() || ca.len() == 1 || periods.len() == 1,
307 length_mismatch = "list.shift",
308 ca.len(),
309 periods.len()
310 );
311
312 let mut ca = Cow::Borrowed(ca);
314 if ca.len() == 1 && periods.len() != 1 {
315 ca = Cow::Owned(ca.new_from_index(0, periods.len()));
317 }
318 let ca = ca.as_ref();
319
320 let out = match periods.len() {
321 1 => {
322 if let Some(periods) = periods.get(0) {
323 ca.apply_amortized(|s| s.as_ref().shift(periods))
324 } else {
325 ListChunked::full_null_with_dtype(ca.name().clone(), ca.len(), ca.inner_dtype())
326 }
327 },
328 _ => ca.zip_and_apply_amortized(periods, |opt_s, opt_periods| {
329 match (opt_s, opt_periods) {
330 (Some(s), Some(periods)) => Some(s.as_ref().shift(periods)),
331 _ => None,
332 }
333 }),
334 };
335 Ok(self.same_type(out))
336 }
337
338 fn lst_slice(&self, offset: i64, length: usize) -> ListChunked {
339 let ca = self.as_list();
340 let out = ca.apply_amortized(|s| s.as_ref().slice(offset, length));
341 self.same_type(out)
342 }
343
344 fn lst_lengths(&self) -> IdxCa {
345 let ca = self.as_list();
346
347 let ca_validity = ca.rechunk_validity();
348
349 if ca_validity.as_ref().is_some_and(|x| x.set_bits() == 0) {
350 return IdxCa::full_null(ca.name().clone(), ca.len());
351 }
352
353 let mut lengths = Vec::with_capacity(ca.len());
354 ca.downcast_iter().for_each(|arr| {
355 let offsets = arr.offsets().as_slice();
356 let mut last = offsets[0];
357 for o in &offsets[1..] {
358 lengths.push((*o - last) as IdxSize);
359 last = *o;
360 }
361 });
362
363 let arr = IdxArr::from_vec(lengths).with_validity(ca_validity);
364 IdxCa::with_chunk(ca.name().clone(), arr)
365 }
366
367 fn lst_get(&self, idx: i64, null_on_oob: bool) -> PolarsResult<Series> {
372 let ca = self.as_list();
373 if !null_on_oob && ca.downcast_iter().any(|arr| index_is_oob(arr, idx)) {
374 polars_bail!(ComputeError: "get index is out of bounds");
375 }
376
377 let chunks = ca
378 .downcast_iter()
379 .map(|arr| sublist_get(arr, idx))
380 .collect::<Vec<_>>();
381
382 let s = Series::try_from((ca.name().clone(), chunks)).unwrap();
383 unsafe { s.from_physical_unchecked(ca.inner_dtype()) }
385 }
386
387 #[cfg(feature = "list_gather")]
388 fn lst_gather_every(&self, n: &IdxCa, offset: &IdxCa) -> PolarsResult<Series> {
389 let list_ca = self.as_list();
390 let out = match (n.len(), offset.len()) {
391 (1, 1) => match (n.get(0), offset.get(0)) {
392 (Some(n), Some(offset)) => list_ca.try_apply_amortized(|s| {
393 s.as_ref().gather_every(n as usize, offset as usize)
394 })?,
395 _ => ListChunked::full_null_with_dtype(
396 list_ca.name().clone(),
397 list_ca.len(),
398 list_ca.inner_dtype(),
399 ),
400 },
401 (1, len_offset) if len_offset == list_ca.len() => {
402 if let Some(n) = n.get(0) {
403 list_ca.try_zip_and_apply_amortized(offset, |opt_s, opt_offset| {
404 match (opt_s, opt_offset) {
405 (Some(s), Some(offset)) => {
406 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
407 },
408 _ => Ok(None),
409 }
410 })?
411 } else {
412 ListChunked::full_null_with_dtype(
413 list_ca.name().clone(),
414 list_ca.len(),
415 list_ca.inner_dtype(),
416 )
417 }
418 },
419 (len_n, 1) if len_n == list_ca.len() => {
420 if let Some(offset) = offset.get(0) {
421 list_ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
422 (Some(s), Some(n)) => {
423 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
424 },
425 _ => Ok(None),
426 })?
427 } else {
428 ListChunked::full_null_with_dtype(
429 list_ca.name().clone(),
430 list_ca.len(),
431 list_ca.inner_dtype(),
432 )
433 }
434 },
435 (len_n, len_offset) if len_n == len_offset && len_n == list_ca.len() => list_ca
436 .try_binary_zip_and_apply_amortized(
437 n,
438 offset,
439 |opt_s, opt_n, opt_offset| match (opt_s, opt_n, opt_offset) {
440 (Some(s), Some(n), Some(offset)) => {
441 Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))
442 },
443 _ => Ok(None),
444 },
445 )?,
446 _ => {
447 polars_bail!(ComputeError: "The lengths of `n` and `offset` should be 1 or equal to the length of list.")
448 },
449 };
450 Ok(out.into_series())
451 }
452
453 #[cfg(feature = "list_gather")]
454 fn lst_gather(&self, idx: &Series, null_on_oob: bool) -> PolarsResult<Series> {
455 let list_ca = self.as_list();
456 let idx_ca = idx.list()?;
457
458 polars_ensure!(
459 idx_ca.inner_dtype().is_integer(),
460 ComputeError: "cannot use dtype `{}` as an index", idx_ca.inner_dtype()
461 );
462
463 let index_typed_index = |idx: &Series| {
464 let idx = idx.cast(&IDX_DTYPE).unwrap();
465 {
466 list_ca
467 .amortized_iter()
468 .map(|s| {
469 s.map(|s| {
470 let s = s.as_ref();
471 take_series(s, idx.clone(), null_on_oob)
472 })
473 .transpose()
474 })
475 .collect::<PolarsResult<ListChunked>>()
476 .map(|mut ca| {
477 ca.rename(list_ca.name().clone());
478 ca.into_series()
479 })
480 }
481 };
482
483 match (list_ca.len(), idx_ca.len()) {
484 (1, _) => {
485 let mut out = if list_ca.has_nulls() {
486 ListChunked::full_null_with_dtype(
487 PlSmallStr::EMPTY,
488 idx.len(),
489 list_ca.inner_dtype(),
490 )
491 } else {
492 let s = list_ca.explode(ExplodeOptions {
493 empty_as_null: true,
494 keep_nulls: true,
495 })?;
496 idx_ca
497 .into_iter()
498 .map(|opt_idx| {
499 opt_idx
500 .map(|idx| take_series(&s, idx, null_on_oob))
501 .transpose()
502 })
503 .collect::<PolarsResult<ListChunked>>()?
504 };
505 out.rename(list_ca.name().clone());
506 Ok(out.into_series())
507 },
508 (_, 1) => {
509 let idx_ca = idx_ca.explode(ExplodeOptions {
510 empty_as_null: true,
511 keep_nulls: true,
512 })?;
513
514 use DataType as D;
515 match idx_ca.dtype() {
516 D::UInt32 | D::UInt64 => index_typed_index(&idx_ca),
517 dt if dt.is_signed_integer() => {
518 if let Some(min) = idx_ca.min::<i64>().unwrap() {
519 if min >= 0 {
520 index_typed_index(&idx_ca)
521 } else {
522 let mut out = {
523 list_ca
524 .amortized_iter()
525 .map(|opt_s| {
526 opt_s
527 .map(|s| {
528 take_series(
529 s.as_ref(),
530 idx_ca.clone(),
531 null_on_oob,
532 )
533 })
534 .transpose()
535 })
536 .collect::<PolarsResult<ListChunked>>()?
537 };
538 out.rename(list_ca.name().clone());
539 Ok(out.into_series())
540 }
541 } else {
542 polars_bail!(ComputeError: "all indices are null");
543 }
544 },
545 dt => polars_bail!(ComputeError: "cannot use dtype `{dt}` as an index"),
546 }
547 },
548 (a, b) if a == b => {
549 let mut out = {
550 list_ca
551 .amortized_iter()
552 .zip(idx_ca)
553 .map(|(opt_s, opt_idx)| {
554 {
555 match (opt_s, opt_idx) {
556 (Some(s), Some(idx)) => {
557 Some(take_series(s.as_ref(), idx, null_on_oob))
558 },
559 _ => None,
560 }
561 }
562 .transpose()
563 })
564 .collect::<PolarsResult<ListChunked>>()?
565 };
566 out.rename(list_ca.name().clone());
567 Ok(out.into_series())
568 },
569 (a, b) => polars_bail!(length_mismatch = "list.gather", a, b),
570 }
571 }
572
573 #[cfg(feature = "list_drop_nulls")]
574 fn lst_drop_nulls(&self) -> ListChunked {
575 let list_ca = self.as_list();
576
577 list_ca.apply_amortized(|s| s.as_ref().drop_nulls())
578 }
579
580 #[cfg(feature = "list_sample")]
581 fn lst_sample_n(
582 &self,
583 n: &Series,
584 with_replacement: bool,
585 shuffle: bool,
586 seed: Option<u64>,
587 ) -> PolarsResult<ListChunked> {
588 use std::borrow::Cow;
589
590 let ca = self.as_list();
591
592 let n_s = n.cast(&IDX_DTYPE)?;
593 let n = n_s.idx()?;
594
595 polars_ensure!(
596 ca.len() == n.len() || ca.len() == 1 || n.len() == 1,
597 length_mismatch = "list.sample(n)",
598 ca.len(),
599 n.len()
600 );
601
602 let mut ca = Cow::Borrowed(ca);
604 if ca.len() == 1 && n.len() != 1 {
605 ca = Cow::Owned(ca.new_from_index(0, n.len()));
607 }
608 let ca = ca.as_ref();
609
610 let out = match n.len() {
611 1 => {
612 if let Some(n) = n.get(0) {
613 ca.try_apply_amortized(|s| {
614 s.as_ref()
615 .sample_n(n as usize, with_replacement, shuffle, seed)
616 })
617 } else {
618 Ok(ListChunked::full_null_with_dtype(
619 ca.name().clone(),
620 ca.len(),
621 ca.inner_dtype(),
622 ))
623 }
624 },
625 _ => ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {
626 (Some(s), Some(n)) => s
627 .as_ref()
628 .sample_n(n as usize, with_replacement, shuffle, seed)
629 .map(Some),
630 _ => Ok(None),
631 }),
632 };
633 out.map(|ok| self.same_type(ok))
634 }
635
636 #[cfg(feature = "list_sample")]
637 fn lst_sample_fraction(
638 &self,
639 fraction: &Series,
640 with_replacement: bool,
641 shuffle: bool,
642 seed: Option<u64>,
643 ) -> PolarsResult<ListChunked> {
644 use std::borrow::Cow;
645
646 let ca = self.as_list();
647
648 let fraction_s = fraction.cast(&DataType::Float64)?;
649 let fraction = fraction_s.f64()?;
650
651 polars_ensure!(
652 ca.len() == fraction.len() || ca.len() == 1 || fraction.len() == 1,
653 length_mismatch = "list.sample(fraction)",
654 ca.len(),
655 fraction.len()
656 );
657
658 let mut ca = Cow::Borrowed(ca);
660 if ca.len() == 1 && fraction.len() != 1 {
661 ca = Cow::Owned(ca.new_from_index(0, fraction.len()));
663 }
664 let ca = ca.as_ref();
665
666 let out = match fraction.len() {
667 1 => {
668 if let Some(fraction) = fraction.get(0) {
669 ca.try_apply_amortized(|s| {
670 let n = (s.as_ref().len() as f64 * fraction) as usize;
671 s.as_ref().sample_n(n, with_replacement, shuffle, seed)
672 })
673 } else {
674 Ok(ListChunked::full_null_with_dtype(
675 ca.name().clone(),
676 ca.len(),
677 ca.inner_dtype(),
678 ))
679 }
680 },
681 _ => ca.try_zip_and_apply_amortized(fraction, |opt_s, opt_n| match (opt_s, opt_n) {
682 (Some(s), Some(fraction)) => {
683 let n = (s.as_ref().len() as f64 * fraction) as usize;
684 s.as_ref()
685 .sample_n(n, with_replacement, shuffle, seed)
686 .map(Some)
687 },
688 _ => Ok(None),
689 }),
690 };
691 out.map(|ok| self.same_type(ok))
692 }
693
694 fn lst_concat(&self, other: &[Column]) -> PolarsResult<ListChunked> {
695 let ca = self.as_list();
696 let other_len = other.len();
697 let length = ca.len();
698 let mut other = other.to_vec();
699 let mut inner_super_type = ca.inner_dtype().clone();
700
701 for s in &other {
702 match s.dtype() {
703 DataType::List(inner_type) => {
704 inner_super_type = try_get_supertype(&inner_super_type, inner_type)?;
705 },
706 dt => {
707 inner_super_type = try_get_supertype(&inner_super_type, dt)?;
708 },
709 }
710 }
711
712 let dtype = &DataType::List(Box::new(inner_super_type.clone()));
714 let ca = ca.cast(dtype)?;
715 let ca = ca.list().unwrap();
716
717 let out = if other.iter().all(|s| s.len() == 1) && ca.len() != 1 {
720 cast_rhs(&mut other, &inner_super_type, dtype, length, false)?;
721 let to_append = other
722 .iter()
723 .filter_map(|s| {
724 let lst = s.list().unwrap();
725 unsafe {
727 lst.get_as_series(0)
728 .map(|s| s.from_physical_unchecked(&inner_super_type).unwrap())
729 }
730 })
731 .collect::<Vec<_>>();
732
733 if to_append.len() != other_len {
735 return Ok(ListChunked::full_null_with_dtype(
736 ca.name().clone(),
737 length,
738 &inner_super_type,
739 ));
740 }
741
742 let vals_size_other = other
743 .iter()
744 .map(|s| s.list().unwrap().get_values_size())
745 .sum::<usize>();
746
747 let mut builder = get_list_builder(
748 &inner_super_type,
749 ca.get_values_size() + vals_size_other + 1,
750 length,
751 ca.name().clone(),
752 );
753 ca.into_iter().for_each(|opt_s| {
754 let opt_s = opt_s.map(|mut s| {
755 for append in &to_append {
756 s.append(append).unwrap();
757 }
758 match inner_super_type {
759 #[cfg(feature = "dtype-struct")]
761 DataType::Struct(_) => s = s.rechunk(),
762 _ => {},
764 }
765 s
766 });
767 builder.append_opt_series(opt_s.as_ref()).unwrap();
768 });
769 builder.finish()
770 } else {
771 cast_rhs(&mut other, &inner_super_type, dtype, length, true)?;
773
774 let vals_size_other = other
775 .iter()
776 .map(|s| s.list().unwrap().get_values_size())
777 .sum::<usize>();
778 let mut iters = Vec::with_capacity(other_len + 1);
779
780 for s in other.iter_mut() {
781 iters.push(s.list()?.amortized_iter())
782 }
783 let mut first_iter: Box<dyn PolarsIterator<Item = Option<Series>>> = ca.into_iter();
784 let mut builder = get_list_builder(
785 &inner_super_type,
786 ca.get_values_size() + vals_size_other + 1,
787 length,
788 ca.name().clone(),
789 );
790
791 for _ in 0..ca.len() {
792 let mut acc = match first_iter.next().unwrap() {
793 Some(s) => s,
794 None => {
795 builder.append_null();
796 for it in &mut iters {
798 it.next().unwrap();
799 }
800 continue;
801 },
802 };
803
804 let mut has_nulls = false;
805 for it in &mut iters {
806 match it.next().unwrap() {
807 Some(s) => {
808 if !has_nulls {
809 acc.append(s.as_ref())?;
810 }
811 },
812 None => {
813 has_nulls = true;
814 },
815 }
816 }
817 if has_nulls {
818 builder.append_null();
819 continue;
820 }
821
822 match inner_super_type {
823 #[cfg(feature = "dtype-struct")]
825 DataType::Struct(_) => acc = acc.rechunk(),
826 _ => {},
828 }
829 builder.append_series(&acc).unwrap();
830 }
831 builder.finish()
832 };
833 Ok(out)
834 }
835}
836
837impl ListNameSpaceImpl for ListChunked {}
838
839#[cfg(feature = "list_gather")]
840fn take_series(s: &Series, idx: Series, null_on_oob: bool) -> PolarsResult<Series> {
841 let len = s.len();
842 let idx = cast_index(idx, len, null_on_oob)?;
843 let idx = idx.idx().unwrap();
844 s.take(idx)
845}
846
847#[cfg(feature = "list_gather")]
848fn cast_signed_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series
849where
850 T::Native: Copy + PartialOrd + PartialEq + NumCast + Signed + Zero,
851{
852 idx.iter()
853 .map(|opt_idx| opt_idx.and_then(|idx| idx.negative_to_usize(len).map(|idx| idx as IdxSize)))
854 .collect::<IdxCa>()
855 .into_series()
856}
857
858#[cfg(feature = "list_gather")]
859fn cast_unsigned_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series
860where
861 T::Native: Copy + PartialOrd + ToPrimitive,
862{
863 idx.iter()
864 .map(|opt_idx| {
865 opt_idx.and_then(|idx| {
866 let idx = idx.to_usize().unwrap();
867 if idx >= len {
868 None
869 } else {
870 Some(idx as IdxSize)
871 }
872 })
873 })
874 .collect::<IdxCa>()
875 .into_series()
876}
877
878#[cfg(feature = "list_gather")]
879fn cast_index(idx: Series, len: usize, null_on_oob: bool) -> PolarsResult<Series> {
880 let idx_null_count = idx.null_count();
881 use DataType::*;
882 let out = match idx.dtype() {
883 #[cfg(feature = "big_idx")]
884 UInt32 => {
885 if null_on_oob {
886 let a = idx.u32().unwrap();
887 cast_unsigned_index_ca(a, len)
888 } else {
889 idx.cast(&IDX_DTYPE).unwrap()
890 }
891 },
892 #[cfg(feature = "big_idx")]
893 UInt64 => {
894 if null_on_oob {
895 let a = idx.u64().unwrap();
896 cast_unsigned_index_ca(a, len)
897 } else {
898 idx
899 }
900 },
901 #[cfg(not(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.cast(&IDX_DTYPE).unwrap()
908 }
909 },
910 #[cfg(not(feature = "big_idx"))]
911 UInt32 => {
912 if null_on_oob {
913 let a = idx.u32().unwrap();
914 cast_unsigned_index_ca(a, len)
915 } else {
916 idx
917 }
918 },
919 dt if dt.is_unsigned_integer() => idx.cast(&IDX_DTYPE).unwrap(),
920 Int8 => {
921 let a = idx.i8().unwrap();
922 cast_signed_index_ca(a, len)
923 },
924 Int16 => {
925 let a = idx.i16().unwrap();
926 cast_signed_index_ca(a, len)
927 },
928 Int32 => {
929 let a = idx.i32().unwrap();
930 cast_signed_index_ca(a, len)
931 },
932 Int64 => {
933 let a = idx.i64().unwrap();
934 cast_signed_index_ca(a, len)
935 },
936 _ => {
937 unreachable!()
938 },
939 };
940 polars_ensure!(
941 out.null_count() == idx_null_count || null_on_oob,
942 OutOfBounds: "gather indices are out of bounds"
943 );
944 Ok(out)
945}
946
947