Path: blob/main/crates/polars-ops/src/series/ops/index.rs
8458 views
use arrow::array::Array;1use arrow::bitmap::BitmapBuilder;2use arrow::compute::utils::combine_validities_and;3use arrow::datatypes::IdxArr;4use num_traits::{Bounded, ToPrimitive, Zero};5use polars_core::error::{PolarsResult, polars_bail, polars_ensure};6use polars_core::prelude::{ChunkedArray, IdxCa, IdxSize, PolarsIntegerType, Series};7use polars_core::with_match_physical_integer_polars_type;8use polars_utils::select::select_unpredictable;9use polars_utils::vec::PushUnchecked;1011/// UNSIGNED conversion:12/// - `0 <= v < target_len` → `Some(v)`13/// - `v >= target_len` → `None`14///15/// SIGNED conversion with Python-style negative semantics:16/// - `v < -target_len` → `None`17/// - `-target_len <= v < 0` → `Some(target_len + v)`18/// - `0 <= v < target_len` → `Some(v)`19/// - `v >= target_len` → `None`20pub fn convert_and_bound_idx_ca<T>(21ca: &ChunkedArray<T>,22target_len: usize,23null_on_oob: bool,24) -> PolarsResult<IdxCa>25where26T: PolarsIntegerType,27T::Native: ToPrimitive,28{29let mut out = Vec::with_capacity(ca.len());30let mut in_bounds = BitmapBuilder::with_capacity(ca.len());31assert!(target_len < IdxSize::MAX as usize);3233let unsigned = T::Native::min_value() == T::Native::zero(); // Optimized to constant by compiler.34if unsigned {35let len_u64 = target_len as u64;36for arr in ca.downcast_iter() {37for v in arr.values().iter() {38// SAFETY: we reserved.39unsafe {40if let Some(v_u64) = v.to_u64() {41// Usually infallible.42out.push_unchecked(v_u64 as IdxSize);43in_bounds.push_unchecked(v_u64 < len_u64);44} else {45in_bounds.push_unchecked(false);46}47}48}49}50} else {51let len_i64 = target_len as i64;52for arr in ca.downcast_iter() {53for v in arr.values().iter() {54// SAFETY: we reserved.55unsafe {56if let Some(v_i64) = v.to_i64() {57// Usually infallible.58let mut shifted = v_i64;59shifted += select_unpredictable(v_i64 < 0, len_i64, 0);60out.push_unchecked(shifted as IdxSize);61in_bounds.push_unchecked((v_i64 >= -len_i64) & (v_i64 < len_i64));62} else {63in_bounds.push_unchecked(false);64}65}66}67}68}6970let idx_arr = IdxArr::from_vec(out);71let in_bounds_valid = in_bounds.into_opt_validity();72let ca_valid = ca.rechunk_validity();73let valid = combine_validities_and(in_bounds_valid.as_ref(), ca_valid.as_ref());74let out = idx_arr.with_validity(valid);7576if !null_on_oob && out.null_count() != ca.null_count() {77polars_bail!(78OutOfBounds: "gather indices are out of bounds"79);80}8182Ok(out.into())83}8485/// Convert arbitrary integer Series into IdxCa, using `target_len` as logical length.86///87/// - All OOB indices are mapped to null in `convert_*`.88/// - We track null counts before and after:89/// - if `null_on_oob == true`, extra nulls are expected and we just return.90/// - if `null_on_oob == false` and new nulls appear, we raise OutOfBounds.91pub fn convert_and_bound_index(92s: &Series,93target_len: usize,94null_on_oob: bool,95) -> PolarsResult<IdxCa> {96let dtype = s.dtype();97polars_ensure!(98dtype.is_integer(),99InvalidOperation: "expected integers as index"100);101102with_match_physical_integer_polars_type!(dtype, |$T| {103let ca: &ChunkedArray<$T> = s.as_ref().as_ref();104convert_and_bound_idx_ca(ca, target_len, null_on_oob)105})106}107108109