Path: blob/main/crates/polars-ops/src/chunked_array/list/namespace.rs
8412 views
use std::borrow::Cow;1use std::fmt::Write;23use arrow::array::ValueSize;4#[cfg(feature = "list_gather")]5use num_traits::ToPrimitive;6#[cfg(feature = "list_gather")]7use num_traits::{NumCast, Signed, Zero};8use polars_compute::gather::sublist::list::{index_is_oob, sublist_get};9use polars_core::chunked_array::builder::get_list_builder;10#[cfg(feature = "diff")]11use polars_core::series::ops::NullBehavior;12use polars_core::utils::try_get_supertype;1314use super::*;15#[cfg(feature = "list_any_all")]16use crate::chunked_array::list::any_all::*;17use crate::chunked_array::list::min_max::{list_max_function, list_min_function};18use crate::chunked_array::list::sum_mean::sum_with_nulls;19#[cfg(feature = "diff")]20use crate::prelude::diff;21use crate::prelude::list::sum_mean::{mean_list_numerical, sum_list_numerical};22use crate::series::ArgAgg;2324pub(super) fn has_inner_nulls(ca: &ListChunked) -> bool {25for arr in ca.downcast_iter() {26if arr.values().null_count() > 0 {27return true;28}29}30false31}3233fn cast_rhs(34other: &mut [Column],35inner_type: &DataType,36dtype: &DataType,37length: usize,38allow_broadcast: bool,39) -> PolarsResult<()> {40for s in other.iter_mut() {41// make sure that inner types match before we coerce into list42if !matches!(s.dtype(), DataType::List(_)) {43*s = s.cast(inner_type)?44}45if !matches!(s.dtype(), DataType::List(_)) && s.dtype() == inner_type {46// coerce to list JIT47*s = s48.reshape_list(&[ReshapeDimension::Infer, ReshapeDimension::new_dimension(1)])49.unwrap();50}51if s.dtype() != dtype {52*s = s.cast(dtype).map_err(|e| {53polars_err!(54SchemaMismatch:55"cannot concat `{}` into a list of `{}`: {}",56s.dtype(),57dtype,58e59)60})?;61}6263if s.len() != length {64polars_ensure!(65s.len() == 1,66ShapeMismatch: "series length {} does not match expected length of {}",67s.len(), length68);69if allow_broadcast {70// broadcast JIT71*s = s.new_from_index(0, length)72}73// else do nothing74}75}76Ok(())77}7879pub trait ListNameSpaceImpl: AsList {80/// In case the inner dtype [`DataType::String`], the individual items will be joined into a81/// single string separated by `separator`.82fn lst_join(83&self,84separator: &StringChunked,85ignore_nulls: bool,86) -> PolarsResult<StringChunked> {87let ca = self.as_list();88match ca.inner_dtype() {89DataType::String => match separator.len() {901 => match separator.get(0) {91Some(separator) => self.join_literal(separator, ignore_nulls),92_ => Ok(StringChunked::full_null(ca.name().clone(), ca.len())),93},94_ => self.join_many(separator, ignore_nulls),95},96dt => polars_bail!(op = "`lst.join`", got = dt, expected = "String"),97}98}99100fn join_literal(&self, separator: &str, ignore_nulls: bool) -> PolarsResult<StringChunked> {101let ca = self.as_list();102// used to amortize heap allocs103let mut buf = String::with_capacity(128);104let mut builder = StringChunkedBuilder::new(ca.name().clone(), ca.len());105106ca.for_each_amortized(|opt_s| {107let opt_val = opt_s.and_then(|s| {108// make sure that we don't write values of previous iteration109buf.clear();110let ca = s.as_ref().str().unwrap();111112if ca.null_count() != 0 && !ignore_nulls {113return None;114}115116for arr in ca.downcast_iter() {117for val in arr.non_null_values_iter() {118buf.write_str(val).unwrap();119buf.write_str(separator).unwrap();120}121}122123// last value should not have a separator, so slice that off124// saturating sub because there might have been nothing written.125Some(&buf[..buf.len().saturating_sub(separator.len())])126});127builder.append_option(opt_val)128});129Ok(builder.finish())130}131132fn join_many(133&self,134separator: &StringChunked,135ignore_nulls: bool,136) -> PolarsResult<StringChunked> {137let ca = self.as_list();138// used to amortize heap allocs139let mut buf = String::with_capacity(128);140let mut builder = StringChunkedBuilder::new(ca.name().clone(), ca.len());141{142ca.amortized_iter()143.zip(separator)144.for_each(|(opt_s, opt_sep)| match opt_sep {145Some(separator) => {146let opt_val = opt_s.and_then(|s| {147// make sure that we don't write values of previous iteration148buf.clear();149let ca = s.as_ref().str().unwrap();150151if ca.null_count() != 0 && !ignore_nulls {152return None;153}154155for arr in ca.downcast_iter() {156for val in arr.non_null_values_iter() {157buf.write_str(val).unwrap();158buf.write_str(separator).unwrap();159}160}161162// last value should not have a separator, so slice that off163// saturating sub because there might have been nothing written.164Some(&buf[..buf.len().saturating_sub(separator.len())])165});166builder.append_option(opt_val)167},168_ => builder.append_null(),169})170}171Ok(builder.finish())172}173174fn lst_max(&self) -> PolarsResult<Series> {175list_max_function(self.as_list())176}177178#[cfg(feature = "list_any_all")]179fn lst_all(&self) -> PolarsResult<Series> {180let ca = self.as_list();181list_all(ca)182}183184#[cfg(feature = "list_any_all")]185fn lst_any(&self) -> PolarsResult<Series> {186let ca = self.as_list();187list_any(ca)188}189190fn lst_min(&self) -> PolarsResult<Series> {191list_min_function(self.as_list())192}193194fn lst_sum(&self) -> PolarsResult<Series> {195let ca = self.as_list();196197if has_inner_nulls(ca) {198return sum_with_nulls(ca, ca.inner_dtype());199};200201match ca.inner_dtype() {202DataType::Boolean => Ok(count_boolean_bits(ca).into_series()),203dt if dt.is_primitive_numeric() => Ok(sum_list_numerical(ca, dt)),204dt => sum_with_nulls(ca, dt),205}206}207208fn lst_mean(&self) -> Series {209let ca = self.as_list();210211if has_inner_nulls(ca) {212return sum_mean::mean_with_nulls(ca);213};214215match ca.inner_dtype() {216dt if dt.is_primitive_numeric() => mean_list_numerical(ca, dt),217_ => sum_mean::mean_with_nulls(ca),218}219}220221fn lst_median(&self) -> Series {222let ca = self.as_list();223dispersion::median_with_nulls(ca)224}225226fn lst_std(&self, ddof: u8) -> Series {227let ca = self.as_list();228dispersion::std_with_nulls(ca, ddof)229}230231fn lst_var(&self, ddof: u8) -> PolarsResult<Series> {232let ca = self.as_list();233dispersion::var_with_nulls(ca, ddof)234}235236fn same_type(&self, out: ListChunked) -> ListChunked {237let ca = self.as_list();238let dtype = ca.dtype();239if out.dtype() != dtype {240out.cast(ca.dtype()).unwrap().list().unwrap().clone()241} else {242out243}244}245246fn lst_sort(&self, options: SortOptions) -> PolarsResult<ListChunked> {247let ca = self.as_list();248// SAFETY: `sort_with`` doesn't change the dtype249let out = unsafe { ca.try_apply_amortized_same_type(|s| s.as_ref().sort_with(options))? };250Ok(self.same_type(out))251}252253#[must_use]254fn lst_reverse(&self) -> ListChunked {255let ca = self.as_list();256// SAFETY: `reverse` doesn't change the dtype257unsafe { ca.apply_amortized_same_type(|s| s.as_ref().reverse()) }258}259260fn lst_n_unique(&self) -> PolarsResult<IdxCa> {261let ca = self.as_list();262ca.try_apply_amortized_generic(|s| {263let opt_v = s.map(|s| s.as_ref().n_unique()).transpose()?;264Ok(opt_v.map(|idx| idx as IdxSize))265})266}267268fn lst_unique(&self) -> PolarsResult<ListChunked> {269let ca = self.as_list();270// SAFETY: `unique` doesn't change the dtype271let out = unsafe { ca.try_apply_amortized_same_type(|s| s.as_ref().unique())? };272Ok(self.same_type(out))273}274275fn lst_unique_stable(&self) -> PolarsResult<ListChunked> {276let ca = self.as_list();277// SAFETY: `unique_stable` doesn't change the dtype278let out = unsafe { ca.try_apply_amortized_same_type(|s| s.as_ref().unique_stable())? };279Ok(self.same_type(out))280}281282fn lst_arg_min(&self) -> IdxCa {283let ca = self.as_list();284ca.apply_amortized_generic(|opt_s| {285opt_s.and_then(|s| s.as_ref().arg_min().map(|idx| idx as IdxSize))286})287}288289fn lst_arg_max(&self) -> IdxCa {290let ca = self.as_list();291ca.apply_amortized_generic(|opt_s| {292opt_s.and_then(|s| s.as_ref().arg_max().map(|idx| idx as IdxSize))293})294}295296#[cfg(feature = "diff")]297fn lst_diff(&self, n: i64, null_behavior: NullBehavior) -> PolarsResult<ListChunked> {298let ca = self.as_list();299ca.try_apply_amortized(|s| diff(s.as_ref(), n, null_behavior))300}301302fn lst_shift(&self, periods: &Column) -> PolarsResult<ListChunked> {303let ca = self.as_list();304let periods_s = periods.cast(&DataType::Int64)?;305let periods = periods_s.i64()?;306307polars_ensure!(308ca.len() == periods.len() || ca.len() == 1 || periods.len() == 1,309length_mismatch = "list.shift",310ca.len(),311periods.len()312);313314// Broadcast `self`315let mut ca = Cow::Borrowed(ca);316if ca.len() == 1 && periods.len() != 1 {317// Optimize: Don't broadcast and instead have a special path.318ca = Cow::Owned(ca.new_from_index(0, periods.len()));319}320let ca = ca.as_ref();321322let out = match periods.len() {3231 => {324if let Some(periods) = periods.get(0) {325// SAFETY: `shift` doesn't change the dtype326unsafe { ca.apply_amortized_same_type(|s| s.as_ref().shift(periods)) }327} else {328ListChunked::full_null_with_dtype(ca.name().clone(), ca.len(), ca.inner_dtype())329}330},331_ => ca.zip_and_apply_amortized(periods, |opt_s, opt_periods| {332match (opt_s, opt_periods) {333(Some(s), Some(periods)) => Some(s.as_ref().shift(periods)),334_ => None,335}336}),337};338Ok(self.same_type(out))339}340341fn lst_slice(&self, offset: i64, length: usize) -> ListChunked {342let ca = self.as_list();343// SAFETY: `slice` doesn't change the dtype344unsafe { ca.apply_amortized_same_type(|s| s.as_ref().slice(offset, length)) }345}346347fn lst_lengths(&self) -> IdxCa {348let ca = self.as_list();349350let ca_validity = ca.rechunk_validity();351352if ca_validity.as_ref().is_some_and(|x| x.set_bits() == 0) {353return IdxCa::full_null(ca.name().clone(), ca.len());354}355356let mut lengths = Vec::with_capacity(ca.len());357ca.downcast_iter().for_each(|arr| {358let offsets = arr.offsets().as_slice();359let mut last = offsets[0];360for o in &offsets[1..] {361lengths.push((*o - last) as IdxSize);362last = *o;363}364});365366let arr = IdxArr::from_vec(lengths).with_validity(ca_validity);367IdxCa::with_chunk(ca.name().clone(), arr)368}369370/// Get the value by index in the sublists.371/// So index `0` would return the first item of every sublist372/// and index `-1` would return the last item of every sublist373/// if an index is out of bounds, it will return a `None`.374fn lst_get(&self, idx: i64, null_on_oob: bool) -> PolarsResult<Series> {375let ca = self.as_list();376if !null_on_oob && ca.downcast_iter().any(|arr| index_is_oob(arr, idx)) {377polars_bail!(ComputeError: "get index is out of bounds");378}379380let chunks = ca381.downcast_iter()382.map(|arr| sublist_get(arr, idx))383.collect::<Vec<_>>();384385let s = Series::try_from((ca.name().clone(), chunks)).unwrap();386// SAFETY: every element in list has dtype equal to its inner type387unsafe { s.from_physical_unchecked(ca.inner_dtype()) }388}389390#[cfg(feature = "list_gather")]391fn lst_gather_every(&self, n: &IdxCa, offset: &IdxCa) -> PolarsResult<Series> {392let list_ca = self.as_list();393let out = match (n.len(), offset.len()) {394(1, 1) => match (n.get(0), offset.get(0)) {395(Some(n), Some(offset)) => unsafe {396// SAFETY: `gather_every` doesn't change the dtype397list_ca.try_apply_amortized_same_type(|s| {398s.as_ref().gather_every(n as usize, offset as usize)399})?400},401_ => ListChunked::full_null_with_dtype(402list_ca.name().clone(),403list_ca.len(),404list_ca.inner_dtype(),405),406},407(1, len_offset) if len_offset == list_ca.len() => {408if let Some(n) = n.get(0) {409list_ca.try_zip_and_apply_amortized(offset, |opt_s, opt_offset| {410match (opt_s, opt_offset) {411(Some(s), Some(offset)) => {412Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))413},414_ => Ok(None),415}416})?417} else {418ListChunked::full_null_with_dtype(419list_ca.name().clone(),420list_ca.len(),421list_ca.inner_dtype(),422)423}424},425(len_n, 1) if len_n == list_ca.len() => {426if let Some(offset) = offset.get(0) {427list_ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {428(Some(s), Some(n)) => {429Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))430},431_ => Ok(None),432})?433} else {434ListChunked::full_null_with_dtype(435list_ca.name().clone(),436list_ca.len(),437list_ca.inner_dtype(),438)439}440},441(len_n, len_offset) if len_n == len_offset && len_n == list_ca.len() => list_ca442.try_binary_zip_and_apply_amortized(443n,444offset,445|opt_s, opt_n, opt_offset| match (opt_s, opt_n, opt_offset) {446(Some(s), Some(n), Some(offset)) => {447Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))448},449_ => Ok(None),450},451)?,452_ => {453polars_bail!(ComputeError: "The lengths of `n` and `offset` should be 1 or equal to the length of list.")454},455};456Ok(out.into_series())457}458459#[cfg(feature = "list_gather")]460fn lst_gather(&self, idx: &Series, null_on_oob: bool) -> PolarsResult<Series> {461let list_ca = self.as_list();462let idx_ca = idx.list()?;463464polars_ensure!(465idx_ca.inner_dtype().is_integer(),466ComputeError: "cannot use dtype `{}` as an index", idx_ca.inner_dtype()467);468469let index_typed_index = |idx: &Series| {470let idx = idx.cast(&IDX_DTYPE).unwrap();471{472list_ca473.amortized_iter()474.map(|s| {475s.map(|s| {476let s = s.as_ref();477take_series(s, idx.clone(), null_on_oob)478})479.transpose()480})481.collect::<PolarsResult<ListChunked>>()482.map(|mut ca| {483ca.rename(list_ca.name().clone());484ca.into_series()485})486}487};488489match (list_ca.len(), idx_ca.len()) {490(1, _) => {491let mut out = if list_ca.has_nulls() {492ListChunked::full_null_with_dtype(493PlSmallStr::EMPTY,494idx.len(),495list_ca.inner_dtype(),496)497} else {498let s = list_ca.explode(ExplodeOptions {499empty_as_null: true,500keep_nulls: true,501})?;502idx_ca503.into_iter()504.map(|opt_idx| {505opt_idx506.map(|idx| take_series(&s, idx, null_on_oob))507.transpose()508})509.collect::<PolarsResult<ListChunked>>()?510};511out.rename(list_ca.name().clone());512Ok(out.into_series())513},514(_, 1) => {515let idx_ca = idx_ca.explode(ExplodeOptions {516empty_as_null: true,517keep_nulls: true,518})?;519520use DataType as D;521match idx_ca.dtype() {522D::UInt32 | D::UInt64 => index_typed_index(&idx_ca),523dt if dt.is_signed_integer() => {524if let Some(min) = idx_ca.min::<i64>().unwrap() {525if min >= 0 {526index_typed_index(&idx_ca)527} else {528let mut out = {529list_ca530.amortized_iter()531.map(|opt_s| {532opt_s533.map(|s| {534take_series(535s.as_ref(),536idx_ca.clone(),537null_on_oob,538)539})540.transpose()541})542.collect::<PolarsResult<ListChunked>>()?543};544out.rename(list_ca.name().clone());545Ok(out.into_series())546}547} else {548polars_bail!(ComputeError: "all indices are null");549}550},551dt => polars_bail!(ComputeError: "cannot use dtype `{dt}` as an index"),552}553},554(a, b) if a == b => {555let mut out = {556list_ca557.amortized_iter()558.zip(idx_ca)559.map(|(opt_s, opt_idx)| {560{561match (opt_s, opt_idx) {562(Some(s), Some(idx)) => {563Some(take_series(s.as_ref(), idx, null_on_oob))564},565_ => None,566}567}568.transpose()569})570.collect::<PolarsResult<ListChunked>>()?571};572out.rename(list_ca.name().clone());573Ok(out.into_series())574},575(a, b) => polars_bail!(length_mismatch = "list.gather", a, b),576}577}578579#[cfg(feature = "list_drop_nulls")]580fn lst_drop_nulls(&self) -> ListChunked {581let list_ca = self.as_list();582583// SAFETY: `drop_nulls` doesn't change the dtype584unsafe { list_ca.apply_amortized_same_type(|s| s.as_ref().drop_nulls()) }585}586587#[cfg(feature = "list_sample")]588fn lst_sample_n(589&self,590n: &Series,591with_replacement: bool,592shuffle: bool,593seed: Option<u64>,594) -> PolarsResult<ListChunked> {595use std::borrow::Cow;596597let ca = self.as_list();598599let n_s = n.strict_cast(&IDX_DTYPE)?;600let n = n_s.idx()?;601602polars_ensure!(603ca.len() == n.len() || ca.len() == 1 || n.len() == 1,604length_mismatch = "list.sample(n)",605ca.len(),606n.len()607);608609// Broadcast `self`610let mut ca = Cow::Borrowed(ca);611if ca.len() == 1 && n.len() != 1 {612// Optimize: Don't broadcast and instead have a special path.613ca = Cow::Owned(ca.new_from_index(0, n.len()));614}615let ca = ca.as_ref();616617let out = match n.len() {6181 => {619if let Some(n) = n.get(0) {620unsafe {621// SAFETY: `sample_n` doesn't change the dtype622ca.try_apply_amortized_same_type(|s| {623s.as_ref()624.sample_n(n as usize, with_replacement, shuffle, seed)625})626}627} else {628Ok(ListChunked::full_null_with_dtype(629ca.name().clone(),630ca.len(),631ca.inner_dtype(),632))633}634},635_ => ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {636(Some(s), Some(n)) => s637.as_ref()638.sample_n(n as usize, with_replacement, shuffle, seed)639.map(Some),640_ => Ok(None),641}),642};643out.map(|ok| self.same_type(ok))644}645646#[cfg(feature = "list_sample")]647fn lst_sample_fraction(648&self,649fraction: &Series,650with_replacement: bool,651shuffle: bool,652seed: Option<u64>,653) -> PolarsResult<ListChunked> {654use std::borrow::Cow;655656let ca = self.as_list();657658let fraction_s = fraction.cast(&DataType::Float64)?;659let fraction = fraction_s.f64()?;660661polars_ensure!(662ca.len() == fraction.len() || ca.len() == 1 || fraction.len() == 1,663length_mismatch = "list.sample(fraction)",664ca.len(),665fraction.len()666);667668// Broadcast `self`669let mut ca = Cow::Borrowed(ca);670if ca.len() == 1 && fraction.len() != 1 {671// Optimize: Don't broadcast and instead have a special path.672ca = Cow::Owned(ca.new_from_index(0, fraction.len()));673}674let ca = ca.as_ref();675676let out = match fraction.len() {6771 => {678if let Some(fraction) = fraction.get(0) {679unsafe {680// SAFETY: `sample_n` doesn't change the dtype681ca.try_apply_amortized_same_type(|s| {682let n = (s.as_ref().len() as f64 * fraction) as usize;683s.as_ref().sample_n(n, with_replacement, shuffle, seed)684})685}686} else {687Ok(ListChunked::full_null_with_dtype(688ca.name().clone(),689ca.len(),690ca.inner_dtype(),691))692}693},694_ => ca.try_zip_and_apply_amortized(fraction, |opt_s, opt_n| match (opt_s, opt_n) {695(Some(s), Some(fraction)) => {696let n = (s.as_ref().len() as f64 * fraction) as usize;697s.as_ref()698.sample_n(n, with_replacement, shuffle, seed)699.map(Some)700},701_ => Ok(None),702}),703};704out.map(|ok| self.same_type(ok))705}706707fn lst_concat(&self, other: &[Column]) -> PolarsResult<ListChunked> {708let ca = self.as_list();709let other_len = other.len();710let length = ca.len();711let mut other = other.to_vec();712let mut inner_super_type = ca.inner_dtype().clone();713714for s in &other {715match s.dtype() {716DataType::List(inner_type) => {717inner_super_type = try_get_supertype(&inner_super_type, inner_type)?;718},719dt => {720inner_super_type = try_get_supertype(&inner_super_type, dt)?;721},722}723}724725// cast lhs726let dtype = &DataType::List(Box::new(inner_super_type.clone()));727let ca = ca.cast(dtype)?;728let ca = ca.list().unwrap();729730// broadcasting path in case all unit length731// this path will not expand the series, so saves memory732let out = if other.iter().all(|s| s.len() == 1) && ca.len() != 1 {733cast_rhs(&mut other, &inner_super_type, dtype, length, false)?;734let to_append = other735.iter()736.filter_map(|s| {737let lst = s.list().unwrap();738// SAFETY: previous rhs_cast ensures the type is correct739unsafe {740lst.get_as_series(0)741.map(|s| s.from_physical_unchecked(&inner_super_type).unwrap())742}743})744.collect::<Vec<_>>();745746// there was a None, so all values will be None747if to_append.len() != other_len {748return Ok(ListChunked::full_null_with_dtype(749ca.name().clone(),750length,751&inner_super_type,752));753}754755let vals_size_other = other756.iter()757.map(|s| s.list().unwrap().get_values_size())758.sum::<usize>();759760let mut builder = get_list_builder(761&inner_super_type,762ca.get_values_size() + vals_size_other + 1,763length,764ca.name().clone(),765);766ca.into_iter().for_each(|opt_s| {767let opt_s = opt_s.map(|mut s| {768for append in &to_append {769s.append(append).unwrap();770}771match inner_super_type {772// structs don't have chunks, so we must first rechunk the underlying series773#[cfg(feature = "dtype-struct")]774DataType::Struct(_) => s = s.rechunk(),775// nothing776_ => {},777}778s779});780builder.append_opt_series(opt_s.as_ref()).unwrap();781});782builder.finish()783} else {784// normal path which may contain same length list or unit length lists785cast_rhs(&mut other, &inner_super_type, dtype, length, true)?;786787let vals_size_other = other788.iter()789.map(|s| s.list().unwrap().get_values_size())790.sum::<usize>();791let mut iters = Vec::with_capacity(other_len + 1);792793for s in other.iter_mut() {794iters.push(s.list()?.amortized_iter())795}796let mut first_iter: Box<dyn PolarsIterator<Item = Option<Series>>> = ca.into_iter();797let mut builder = get_list_builder(798&inner_super_type,799ca.get_values_size() + vals_size_other + 1,800length,801ca.name().clone(),802);803804for _ in 0..ca.len() {805let mut acc = match first_iter.next().unwrap() {806Some(s) => s,807None => {808builder.append_null();809// make sure that the iterators advance before we continue810for it in &mut iters {811it.next().unwrap();812}813continue;814},815};816817let mut has_nulls = false;818for it in &mut iters {819match it.next().unwrap() {820Some(s) => {821if !has_nulls {822acc.append(s.as_ref())?;823}824},825None => {826has_nulls = true;827},828}829}830if has_nulls {831builder.append_null();832continue;833}834835match inner_super_type {836// structs don't have chunks, so we must first rechunk the underlying series837#[cfg(feature = "dtype-struct")]838DataType::Struct(_) => acc = acc.rechunk(),839// nothing840_ => {},841}842builder.append_series(&acc).unwrap();843}844builder.finish()845};846Ok(out)847}848}849850impl ListNameSpaceImpl for ListChunked {}851852#[cfg(feature = "list_gather")]853fn take_series(s: &Series, idx: Series, null_on_oob: bool) -> PolarsResult<Series> {854let len = s.len();855let idx = cast_index(idx, len, null_on_oob)?;856let idx = idx.idx().unwrap();857s.take(idx)858}859860#[cfg(feature = "list_gather")]861fn cast_signed_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series862where863T::Native: Copy + PartialOrd + PartialEq + NumCast + Signed + Zero,864{865idx.iter()866.map(|opt_idx| opt_idx.and_then(|idx| idx.negative_to_usize(len).map(|idx| idx as IdxSize)))867.collect::<IdxCa>()868.into_series()869}870871#[cfg(feature = "list_gather")]872fn cast_unsigned_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series873where874T::Native: Copy + PartialOrd + ToPrimitive,875{876idx.iter()877.map(|opt_idx| {878opt_idx.and_then(|idx| {879let idx = idx.to_usize().unwrap();880if idx >= len {881None882} else {883Some(idx as IdxSize)884}885})886})887.collect::<IdxCa>()888.into_series()889}890891#[cfg(feature = "list_gather")]892fn cast_index(idx: Series, len: usize, null_on_oob: bool) -> PolarsResult<Series> {893let idx_null_count = idx.null_count();894use DataType::*;895let out = match idx.dtype() {896#[cfg(feature = "big_idx")]897UInt32 => {898if null_on_oob {899let a = idx.u32().unwrap();900cast_unsigned_index_ca(a, len)901} else {902idx.cast(&IDX_DTYPE).unwrap()903}904},905#[cfg(feature = "big_idx")]906UInt64 => {907if null_on_oob {908let a = idx.u64().unwrap();909cast_unsigned_index_ca(a, len)910} else {911idx912}913},914#[cfg(not(feature = "big_idx"))]915UInt64 => {916if null_on_oob {917let a = idx.u64().unwrap();918cast_unsigned_index_ca(a, len)919} else {920idx.cast(&IDX_DTYPE).unwrap()921}922},923#[cfg(not(feature = "big_idx"))]924UInt32 => {925if null_on_oob {926let a = idx.u32().unwrap();927cast_unsigned_index_ca(a, len)928} else {929idx930}931},932dt if dt.is_unsigned_integer() => idx.cast(&IDX_DTYPE).unwrap(),933Int8 => {934let a = idx.i8().unwrap();935cast_signed_index_ca(a, len)936},937Int16 => {938let a = idx.i16().unwrap();939cast_signed_index_ca(a, len)940},941Int32 => {942let a = idx.i32().unwrap();943cast_signed_index_ca(a, len)944},945Int64 => {946let a = idx.i64().unwrap();947cast_signed_index_ca(a, len)948},949_ => {950unreachable!()951},952};953polars_ensure!(954out.null_count() == idx_null_count || null_on_oob,955OutOfBounds: "gather indices are out of bounds"956);957Ok(out)958}959960// TODO: implement the above for ArrayChunked as well?961962963