Path: blob/main/crates/polars-ops/src/chunked_array/list/namespace.rs
6939 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();248let out = ca.try_apply_amortized(|s| s.as_ref().sort_with(options))?;249Ok(self.same_type(out))250}251252#[must_use]253fn lst_reverse(&self) -> ListChunked {254let ca = self.as_list();255let out = ca.apply_amortized(|s| s.as_ref().reverse());256self.same_type(out)257}258259fn lst_n_unique(&self) -> PolarsResult<IdxCa> {260let ca = self.as_list();261ca.try_apply_amortized_generic(|s| {262let opt_v = s.map(|s| s.as_ref().n_unique()).transpose()?;263Ok(opt_v.map(|idx| idx as IdxSize))264})265}266267fn lst_unique(&self) -> PolarsResult<ListChunked> {268let ca = self.as_list();269let out = ca.try_apply_amortized(|s| s.as_ref().unique())?;270Ok(self.same_type(out))271}272273fn lst_unique_stable(&self) -> PolarsResult<ListChunked> {274let ca = self.as_list();275let out = ca.try_apply_amortized(|s| s.as_ref().unique_stable())?;276Ok(self.same_type(out))277}278279fn lst_arg_min(&self) -> IdxCa {280let ca = self.as_list();281ca.apply_amortized_generic(|opt_s| {282opt_s.and_then(|s| s.as_ref().arg_min().map(|idx| idx as IdxSize))283})284}285286fn lst_arg_max(&self) -> IdxCa {287let ca = self.as_list();288ca.apply_amortized_generic(|opt_s| {289opt_s.and_then(|s| s.as_ref().arg_max().map(|idx| idx as IdxSize))290})291}292293#[cfg(feature = "diff")]294fn lst_diff(&self, n: i64, null_behavior: NullBehavior) -> PolarsResult<ListChunked> {295let ca = self.as_list();296ca.try_apply_amortized(|s| diff(s.as_ref(), n, null_behavior))297}298299fn lst_shift(&self, periods: &Column) -> PolarsResult<ListChunked> {300let ca = self.as_list();301let periods_s = periods.cast(&DataType::Int64)?;302let periods = periods_s.i64()?;303304polars_ensure!(305ca.len() == periods.len() || ca.len() == 1 || periods.len() == 1,306length_mismatch = "list.shift",307ca.len(),308periods.len()309);310311// Broadcast `self`312let mut ca = Cow::Borrowed(ca);313if ca.len() == 1 && periods.len() != 1 {314// Optimize: Don't broadcast and instead have a special path.315ca = Cow::Owned(ca.new_from_index(0, periods.len()));316}317let ca = ca.as_ref();318319let out = match periods.len() {3201 => {321if let Some(periods) = periods.get(0) {322ca.apply_amortized(|s| s.as_ref().shift(periods))323} else {324ListChunked::full_null_with_dtype(ca.name().clone(), ca.len(), ca.inner_dtype())325}326},327_ => ca.zip_and_apply_amortized(periods, |opt_s, opt_periods| {328match (opt_s, opt_periods) {329(Some(s), Some(periods)) => Some(s.as_ref().shift(periods)),330_ => None,331}332}),333};334Ok(self.same_type(out))335}336337fn lst_slice(&self, offset: i64, length: usize) -> ListChunked {338let ca = self.as_list();339let out = ca.apply_amortized(|s| s.as_ref().slice(offset, length));340self.same_type(out)341}342343fn lst_lengths(&self) -> IdxCa {344let ca = self.as_list();345346let ca_validity = ca.rechunk_validity();347348if ca_validity.as_ref().is_some_and(|x| x.set_bits() == 0) {349return IdxCa::full_null(ca.name().clone(), ca.len());350}351352let mut lengths = Vec::with_capacity(ca.len());353ca.downcast_iter().for_each(|arr| {354let offsets = arr.offsets().as_slice();355let mut last = offsets[0];356for o in &offsets[1..] {357lengths.push((*o - last) as IdxSize);358last = *o;359}360});361362let arr = IdxArr::from_vec(lengths).with_validity(ca_validity);363IdxCa::with_chunk(ca.name().clone(), arr)364}365366/// Get the value by index in the sublists.367/// So index `0` would return the first item of every sublist368/// and index `-1` would return the last item of every sublist369/// if an index is out of bounds, it will return a `None`.370fn lst_get(&self, idx: i64, null_on_oob: bool) -> PolarsResult<Series> {371let ca = self.as_list();372if !null_on_oob && ca.downcast_iter().any(|arr| index_is_oob(arr, idx)) {373polars_bail!(ComputeError: "get index is out of bounds");374}375376let chunks = ca377.downcast_iter()378.map(|arr| sublist_get(arr, idx))379.collect::<Vec<_>>();380381let s = Series::try_from((ca.name().clone(), chunks)).unwrap();382// SAFETY: every element in list has dtype equal to its inner type383unsafe { s.from_physical_unchecked(ca.inner_dtype()) }384}385386#[cfg(feature = "list_gather")]387fn lst_gather_every(&self, n: &IdxCa, offset: &IdxCa) -> PolarsResult<Series> {388let list_ca = self.as_list();389let out = match (n.len(), offset.len()) {390(1, 1) => match (n.get(0), offset.get(0)) {391(Some(n), Some(offset)) => list_ca.try_apply_amortized(|s| {392s.as_ref().gather_every(n as usize, offset as usize)393})?,394_ => ListChunked::full_null_with_dtype(395list_ca.name().clone(),396list_ca.len(),397list_ca.inner_dtype(),398),399},400(1, len_offset) if len_offset == list_ca.len() => {401if let Some(n) = n.get(0) {402list_ca.try_zip_and_apply_amortized(offset, |opt_s, opt_offset| {403match (opt_s, opt_offset) {404(Some(s), Some(offset)) => {405Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))406},407_ => Ok(None),408}409})?410} else {411ListChunked::full_null_with_dtype(412list_ca.name().clone(),413list_ca.len(),414list_ca.inner_dtype(),415)416}417},418(len_n, 1) if len_n == list_ca.len() => {419if let Some(offset) = offset.get(0) {420list_ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {421(Some(s), Some(n)) => {422Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))423},424_ => Ok(None),425})?426} else {427ListChunked::full_null_with_dtype(428list_ca.name().clone(),429list_ca.len(),430list_ca.inner_dtype(),431)432}433},434(len_n, len_offset) if len_n == len_offset && len_n == list_ca.len() => list_ca435.try_binary_zip_and_apply_amortized(436n,437offset,438|opt_s, opt_n, opt_offset| match (opt_s, opt_n, opt_offset) {439(Some(s), Some(n), Some(offset)) => {440Ok(Some(s.as_ref().gather_every(n as usize, offset as usize)?))441},442_ => Ok(None),443},444)?,445_ => {446polars_bail!(ComputeError: "The lengths of `n` and `offset` should be 1 or equal to the length of list.")447},448};449Ok(out.into_series())450}451452#[cfg(feature = "list_gather")]453fn lst_gather(&self, idx: &Series, null_on_oob: bool) -> PolarsResult<Series> {454let list_ca = self.as_list();455let idx_ca = idx.list()?;456457polars_ensure!(458idx_ca.inner_dtype().is_integer(),459ComputeError: "cannot use dtype `{}` as an index", idx_ca.inner_dtype()460);461462let index_typed_index = |idx: &Series| {463let idx = idx.cast(&IDX_DTYPE).unwrap();464{465list_ca466.amortized_iter()467.map(|s| {468s.map(|s| {469let s = s.as_ref();470take_series(s, idx.clone(), null_on_oob)471})472.transpose()473})474.collect::<PolarsResult<ListChunked>>()475.map(|mut ca| {476ca.rename(list_ca.name().clone());477ca.into_series()478})479}480};481482match (list_ca.len(), idx_ca.len()) {483(1, _) => {484let mut out = if list_ca.has_nulls() {485ListChunked::full_null_with_dtype(486PlSmallStr::EMPTY,487idx.len(),488list_ca.inner_dtype(),489)490} else {491let s = list_ca.explode(false)?;492idx_ca493.into_iter()494.map(|opt_idx| {495opt_idx496.map(|idx| take_series(&s, idx, null_on_oob))497.transpose()498})499.collect::<PolarsResult<ListChunked>>()?500};501out.rename(list_ca.name().clone());502Ok(out.into_series())503},504(_, 1) => {505let idx_ca = idx_ca.explode(false)?;506507use DataType as D;508match idx_ca.dtype() {509D::UInt32 | D::UInt64 => index_typed_index(&idx_ca),510dt if dt.is_signed_integer() => {511if let Some(min) = idx_ca.min::<i64>().unwrap() {512if min >= 0 {513index_typed_index(&idx_ca)514} else {515let mut out = {516list_ca517.amortized_iter()518.map(|opt_s| {519opt_s520.map(|s| {521take_series(522s.as_ref(),523idx_ca.clone(),524null_on_oob,525)526})527.transpose()528})529.collect::<PolarsResult<ListChunked>>()?530};531out.rename(list_ca.name().clone());532Ok(out.into_series())533}534} else {535polars_bail!(ComputeError: "all indices are null");536}537},538dt => polars_bail!(ComputeError: "cannot use dtype `{dt}` as an index"),539}540},541(a, b) if a == b => {542let mut out = {543list_ca544.amortized_iter()545.zip(idx_ca)546.map(|(opt_s, opt_idx)| {547{548match (opt_s, opt_idx) {549(Some(s), Some(idx)) => {550Some(take_series(s.as_ref(), idx, null_on_oob))551},552_ => None,553}554}555.transpose()556})557.collect::<PolarsResult<ListChunked>>()?558};559out.rename(list_ca.name().clone());560Ok(out.into_series())561},562(a, b) => polars_bail!(length_mismatch = "list.gather", a, b),563}564}565566#[cfg(feature = "list_drop_nulls")]567fn lst_drop_nulls(&self) -> ListChunked {568let list_ca = self.as_list();569570list_ca.apply_amortized(|s| s.as_ref().drop_nulls())571}572573#[cfg(feature = "list_sample")]574fn lst_sample_n(575&self,576n: &Series,577with_replacement: bool,578shuffle: bool,579seed: Option<u64>,580) -> PolarsResult<ListChunked> {581use std::borrow::Cow;582583let ca = self.as_list();584585let n_s = n.cast(&IDX_DTYPE)?;586let n = n_s.idx()?;587588polars_ensure!(589ca.len() == n.len() || ca.len() == 1 || n.len() == 1,590length_mismatch = "list.sample(n)",591ca.len(),592n.len()593);594595// Broadcast `self`596let mut ca = Cow::Borrowed(ca);597if ca.len() == 1 && n.len() != 1 {598// Optimize: Don't broadcast and instead have a special path.599ca = Cow::Owned(ca.new_from_index(0, n.len()));600}601let ca = ca.as_ref();602603let out = match n.len() {6041 => {605if let Some(n) = n.get(0) {606ca.try_apply_amortized(|s| {607s.as_ref()608.sample_n(n as usize, with_replacement, shuffle, seed)609})610} else {611Ok(ListChunked::full_null_with_dtype(612ca.name().clone(),613ca.len(),614ca.inner_dtype(),615))616}617},618_ => ca.try_zip_and_apply_amortized(n, |opt_s, opt_n| match (opt_s, opt_n) {619(Some(s), Some(n)) => s620.as_ref()621.sample_n(n as usize, with_replacement, shuffle, seed)622.map(Some),623_ => Ok(None),624}),625};626out.map(|ok| self.same_type(ok))627}628629#[cfg(feature = "list_sample")]630fn lst_sample_fraction(631&self,632fraction: &Series,633with_replacement: bool,634shuffle: bool,635seed: Option<u64>,636) -> PolarsResult<ListChunked> {637use std::borrow::Cow;638639let ca = self.as_list();640641let fraction_s = fraction.cast(&DataType::Float64)?;642let fraction = fraction_s.f64()?;643644polars_ensure!(645ca.len() == fraction.len() || ca.len() == 1 || fraction.len() == 1,646length_mismatch = "list.sample(fraction)",647ca.len(),648fraction.len()649);650651// Broadcast `self`652let mut ca = Cow::Borrowed(ca);653if ca.len() == 1 && fraction.len() != 1 {654// Optimize: Don't broadcast and instead have a special path.655ca = Cow::Owned(ca.new_from_index(0, fraction.len()));656}657let ca = ca.as_ref();658659let out = match fraction.len() {6601 => {661if let Some(fraction) = fraction.get(0) {662ca.try_apply_amortized(|s| {663let n = (s.as_ref().len() as f64 * fraction) as usize;664s.as_ref().sample_n(n, with_replacement, shuffle, seed)665})666} else {667Ok(ListChunked::full_null_with_dtype(668ca.name().clone(),669ca.len(),670ca.inner_dtype(),671))672}673},674_ => ca.try_zip_and_apply_amortized(fraction, |opt_s, opt_n| match (opt_s, opt_n) {675(Some(s), Some(fraction)) => {676let n = (s.as_ref().len() as f64 * fraction) as usize;677s.as_ref()678.sample_n(n, with_replacement, shuffle, seed)679.map(Some)680},681_ => Ok(None),682}),683};684out.map(|ok| self.same_type(ok))685}686687fn lst_concat(&self, other: &[Column]) -> PolarsResult<ListChunked> {688let ca = self.as_list();689let other_len = other.len();690let length = ca.len();691let mut other = other.to_vec();692let mut inner_super_type = ca.inner_dtype().clone();693694for s in &other {695match s.dtype() {696DataType::List(inner_type) => {697inner_super_type = try_get_supertype(&inner_super_type, inner_type)?;698},699dt => {700inner_super_type = try_get_supertype(&inner_super_type, dt)?;701},702}703}704705// cast lhs706let dtype = &DataType::List(Box::new(inner_super_type.clone()));707let ca = ca.cast(dtype)?;708let ca = ca.list().unwrap();709710// broadcasting path in case all unit length711// this path will not expand the series, so saves memory712let out = if other.iter().all(|s| s.len() == 1) && ca.len() != 1 {713cast_rhs(&mut other, &inner_super_type, dtype, length, false)?;714let to_append = other715.iter()716.filter_map(|s| {717let lst = s.list().unwrap();718// SAFETY: previous rhs_cast ensures the type is correct719unsafe {720lst.get_as_series(0)721.map(|s| s.from_physical_unchecked(&inner_super_type).unwrap())722}723})724.collect::<Vec<_>>();725726// there was a None, so all values will be None727if to_append.len() != other_len {728return Ok(ListChunked::full_null_with_dtype(729ca.name().clone(),730length,731&inner_super_type,732));733}734735let vals_size_other = other736.iter()737.map(|s| s.list().unwrap().get_values_size())738.sum::<usize>();739740let mut builder = get_list_builder(741&inner_super_type,742ca.get_values_size() + vals_size_other + 1,743length,744ca.name().clone(),745);746ca.into_iter().for_each(|opt_s| {747let opt_s = opt_s.map(|mut s| {748for append in &to_append {749s.append(append).unwrap();750}751match inner_super_type {752// structs don't have chunks, so we must first rechunk the underlying series753#[cfg(feature = "dtype-struct")]754DataType::Struct(_) => s = s.rechunk(),755// nothing756_ => {},757}758s759});760builder.append_opt_series(opt_s.as_ref()).unwrap();761});762builder.finish()763} else {764// normal path which may contain same length list or unit length lists765cast_rhs(&mut other, &inner_super_type, dtype, length, true)?;766767let vals_size_other = other768.iter()769.map(|s| s.list().unwrap().get_values_size())770.sum::<usize>();771let mut iters = Vec::with_capacity(other_len + 1);772773for s in other.iter_mut() {774iters.push(s.list()?.amortized_iter())775}776let mut first_iter: Box<dyn PolarsIterator<Item = Option<Series>>> = ca.into_iter();777let mut builder = get_list_builder(778&inner_super_type,779ca.get_values_size() + vals_size_other + 1,780length,781ca.name().clone(),782);783784for _ in 0..ca.len() {785let mut acc = match first_iter.next().unwrap() {786Some(s) => s,787None => {788builder.append_null();789// make sure that the iterators advance before we continue790for it in &mut iters {791it.next().unwrap();792}793continue;794},795};796797let mut has_nulls = false;798for it in &mut iters {799match it.next().unwrap() {800Some(s) => {801if !has_nulls {802acc.append(s.as_ref())?;803}804},805None => {806has_nulls = true;807},808}809}810if has_nulls {811builder.append_null();812continue;813}814815match inner_super_type {816// structs don't have chunks, so we must first rechunk the underlying series817#[cfg(feature = "dtype-struct")]818DataType::Struct(_) => acc = acc.rechunk(),819// nothing820_ => {},821}822builder.append_series(&acc).unwrap();823}824builder.finish()825};826Ok(out)827}828}829830impl ListNameSpaceImpl for ListChunked {}831832#[cfg(feature = "list_gather")]833fn take_series(s: &Series, idx: Series, null_on_oob: bool) -> PolarsResult<Series> {834let len = s.len();835let idx = cast_index(idx, len, null_on_oob)?;836let idx = idx.idx().unwrap();837s.take(idx)838}839840#[cfg(feature = "list_gather")]841fn cast_signed_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series842where843T::Native: Copy + PartialOrd + PartialEq + NumCast + Signed + Zero,844{845idx.iter()846.map(|opt_idx| opt_idx.and_then(|idx| idx.negative_to_usize(len).map(|idx| idx as IdxSize)))847.collect::<IdxCa>()848.into_series()849}850851#[cfg(feature = "list_gather")]852fn cast_unsigned_index_ca<T: PolarsNumericType>(idx: &ChunkedArray<T>, len: usize) -> Series853where854T::Native: Copy + PartialOrd + ToPrimitive,855{856idx.iter()857.map(|opt_idx| {858opt_idx.and_then(|idx| {859let idx = idx.to_usize().unwrap();860if idx >= len {861None862} else {863Some(idx as IdxSize)864}865})866})867.collect::<IdxCa>()868.into_series()869}870871#[cfg(feature = "list_gather")]872fn cast_index(idx: Series, len: usize, null_on_oob: bool) -> PolarsResult<Series> {873let idx_null_count = idx.null_count();874use DataType::*;875let out = match idx.dtype() {876#[cfg(feature = "big_idx")]877UInt32 => {878if null_on_oob {879let a = idx.u32().unwrap();880cast_unsigned_index_ca(a, len)881} else {882idx.cast(&IDX_DTYPE).unwrap()883}884},885#[cfg(feature = "big_idx")]886UInt64 => {887if null_on_oob {888let a = idx.u64().unwrap();889cast_unsigned_index_ca(a, len)890} else {891idx892}893},894#[cfg(not(feature = "big_idx"))]895UInt64 => {896if null_on_oob {897let a = idx.u64().unwrap();898cast_unsigned_index_ca(a, len)899} else {900idx.cast(&IDX_DTYPE).unwrap()901}902},903#[cfg(not(feature = "big_idx"))]904UInt32 => {905if null_on_oob {906let a = idx.u32().unwrap();907cast_unsigned_index_ca(a, len)908} else {909idx910}911},912dt if dt.is_unsigned_integer() => idx.cast(&IDX_DTYPE).unwrap(),913Int8 => {914let a = idx.i8().unwrap();915cast_signed_index_ca(a, len)916},917Int16 => {918let a = idx.i16().unwrap();919cast_signed_index_ca(a, len)920},921Int32 => {922let a = idx.i32().unwrap();923cast_signed_index_ca(a, len)924},925Int64 => {926let a = idx.i64().unwrap();927cast_signed_index_ca(a, len)928},929_ => {930unreachable!()931},932};933polars_ensure!(934out.null_count() == idx_null_count || null_on_oob,935OutOfBounds: "gather indices are out of bounds"936);937Ok(out)938}939940// TODO: implement the above for ArrayChunked as well?941942943