From a0ea486f322cc7839c784e7ac47fed170e9565a2 Mon Sep 17 00:00:00 2001 From: ritchie Date: Wed, 2 Aug 2023 11:42:52 +0200 Subject: [PATCH 1/3] perf(rust, python): fix O(n^2) in sorted check during append --- .../src/chunked_array/object/mod.rs | 8 ++ .../src/chunked_array/ops/append.rs | 4 +- .../src/chunked_array/ops/downcast.rs | 7 ++ .../polars-core/src/chunked_array/ops/mod.rs | 10 ++ .../src/chunked_array/ops/take/take_random.rs | 93 +++++++++++++++++-- .../src/chunked_array/ops/take/take_single.rs | 80 +++++++++++++--- 6 files changed, 177 insertions(+), 25 deletions(-) diff --git a/crates/polars-core/src/chunked_array/object/mod.rs b/crates/polars-core/src/chunked_array/object/mod.rs index ceff52ecc701..9a853d1e01f0 100644 --- a/crates/polars-core/src/chunked_array/object/mod.rs +++ b/crates/polars-core/src/chunked_array/object/mod.rs @@ -69,6 +69,14 @@ where &self.values[self.offset + index] } + pub fn get(&self, index: usize) -> Option<&T> { + if self.is_valid(index) { + Some(unsafe { self.value_unchecked(index) }) + } else { + None + } + } + /// Get a value at a certain index location /// /// # Safety diff --git a/crates/polars-core/src/chunked_array/ops/append.rs b/crates/polars-core/src/chunked_array/ops/append.rs index c2e2ce2ba8a7..fe30b9a47fc9 100644 --- a/crates/polars-core/src/chunked_array/ops/append.rs +++ b/crates/polars-core/src/chunked_array/ops/append.rs @@ -26,7 +26,9 @@ pub(super) fn update_sorted_flag_before_append<'a, T>( // this is safe as we go from &mut borrow to & // because the trait is only implemented for &ChunkedArray let borrow = std::mem::transmute::<&ChunkedArray, &'a ChunkedArray>(ca); - borrow.get_unchecked(ca.len() - 1) + // ensure we don't access with `len() - 1` this will have O(n^2) complexity + // if we append many chunks that are sorted + borrow.last() } }; let start = unsafe { other.get_unchecked(0) }; diff --git a/crates/polars-core/src/chunked_array/ops/downcast.rs b/crates/polars-core/src/chunked_array/ops/downcast.rs index 5856535b4304..52199078ec19 100644 --- a/crates/polars-core/src/chunked_array/ops/downcast.rs +++ b/crates/polars-core/src/chunked_array/ops/downcast.rs @@ -36,6 +36,13 @@ impl<'a, T> Chunks<'a, T> { pub fn len(&self) -> usize { self.chunks.len() } + + pub fn last(&self) -> Option<&'a T> { + self.chunks.last().map(|arr| { + let arr = &**arr; + unsafe { &*(arr as *const dyn Array as *const T) } + }) + } } #[doc(hidden)] diff --git a/crates/polars-core/src/chunked_array/ops/mod.rs b/crates/polars-core/src/chunked_array/ops/mod.rs index eb3ecd2a8590..4f6badd58620 100644 --- a/crates/polars-core/src/chunked_array/ops/mod.rs +++ b/crates/polars-core/src/chunked_array/ops/mod.rs @@ -166,6 +166,11 @@ pub trait TakeRandom { { self.get(index) } + + /// This is much faster if we have many chunks as we don't have to compute the index + /// # Panics + /// Panics if `index >= self.len()` + fn last(&self) -> Option; } // Utility trait because associated type needs a lifetime pub trait TakeRandomUtf8 { @@ -188,6 +193,11 @@ pub trait TakeRandomUtf8 { { self.get(index) } + + /// This is much faster if we have many chunks + /// # Panics + /// Panics if `index >= self.len()` + fn last(&self) -> Option; } /// Fast access by index. diff --git a/crates/polars-core/src/chunked_array/ops/take/take_random.rs b/crates/polars-core/src/chunked_array/ops/take/take_random.rs index 7988a14f1e7a..9248c02129bd 100644 --- a/crates/polars-core/src/chunked_array/ops/take/take_random.rs +++ b/crates/polars-core/src/chunked_array/ops/take/take_random.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use arrow::array::{Array, BooleanArray, ListArray, PrimitiveArray, Utf8Array}; use arrow::bitmap::utils::get_bit_unchecked; use arrow::bitmap::Bitmap; @@ -103,6 +101,14 @@ where Self::Multi(m) => m.get_unchecked(index), } } + + fn last(&self) -> Option { + match self { + Self::SingleNoNull(s) => s.last(), + Self::Single(s) => s.last(), + Self::Multi(m) => m.last(), + } + } } pub enum TakeRandBranch2 { @@ -130,6 +136,12 @@ where Self::Multi(m) => m.get_unchecked(index), } } + fn last(&self) -> Option { + match self { + Self::Single(s) => s.last(), + Self::Multi(m) => m.last(), + } + } } #[allow(clippy::type_complexity)] @@ -187,6 +199,11 @@ impl<'a> TakeRandom for Utf8TakeRandom<'a> { unsafe fn get_unchecked(&self, index: usize) -> Option { take_random_get_unchecked!(self, index) } + fn last(&self) -> Option { + self.chunks + .last() + .and_then(|arr| arr.get(arr.len().saturating_sub(1))) + } } pub struct Utf8TakeRandomSingleChunk<'a> { @@ -209,6 +226,9 @@ impl<'a> TakeRandom for Utf8TakeRandomSingleChunk<'a> { None } } + fn last(&self) -> Option { + self.get(self.arr.len().saturating_sub(1)) + } } impl<'a> IntoTakeRandom<'a> for &'a Utf8Chunked { @@ -251,6 +271,11 @@ impl<'a> TakeRandom for BinaryTakeRandom<'a> { unsafe fn get_unchecked(&self, index: usize) -> Option { take_random_get_unchecked!(self, index) } + fn last(&self) -> Option { + self.chunks + .last() + .and_then(|arr| arr.get(arr.len().saturating_sub(1))) + } } pub struct BinaryTakeRandomSingleChunk<'a> { @@ -273,6 +298,9 @@ impl<'a> TakeRandom for BinaryTakeRandomSingleChunk<'a> { None } } + fn last(&self) -> Option { + self.get(self.arr.len().saturating_sub(1)) + } } impl<'a> IntoTakeRandom<'a> for &'a BinaryChunked { @@ -334,8 +362,11 @@ impl<'a> IntoTakeRandom<'a> for &'a ListChunked { }; TakeRandBranch2::Single(t) } else { + let name = self.name(); + let inner_type = self.inner_dtype(); let t = ListTakeRandom { - ca: self, + name, + inner_type, chunks: chunks.collect(), chunk_lens: self.chunks.iter().map(|a| a.len() as IdxSize).collect(), }; @@ -367,6 +398,11 @@ where unsafe fn get_unchecked(&self, index: usize) -> Option { take_random_get_unchecked!(self, index) } + fn last(&self) -> Option { + self.chunks + .last() + .and_then(|arr| arr.get(arr.len().saturating_sub(1))) + } } pub struct NumTakeRandomCont<'a, T> { @@ -388,6 +424,9 @@ where unsafe fn get_unchecked(&self, index: usize) -> Option { Some(*self.slice.get_unchecked(index)) } + fn last(&self) -> Option { + self.slice.last().copied() + } } pub struct TakeRandomBitmap<'a> { @@ -445,6 +484,9 @@ where None } } + fn last(&self) -> Option { + self.get(self.vals.len().saturating_sub(1)) + } } pub struct BoolTakeRandom<'a> { @@ -464,6 +506,12 @@ impl<'a> TakeRandom for BoolTakeRandom<'a> { unsafe fn get_unchecked(&self, index: usize) -> Option { take_random_get_unchecked!(self, index) } + + fn last(&self) -> Option { + self.chunks + .last() + .and_then(|arr| arr.get(arr.len().saturating_sub(1))) + } } pub struct BoolTakeRandomSingleChunk<'a> { @@ -486,10 +534,14 @@ impl<'a> TakeRandom for BoolTakeRandomSingleChunk<'a> { None } } + fn last(&self) -> Option { + self.arr.get(self.arr.len().saturating_sub(1)) + } } pub struct ListTakeRandom<'a> { - ca: &'a ListChunked, + inner_type: DataType, + name: &'a str, chunks: Vec<&'a ListArray>, chunk_lens: Vec, } @@ -500,18 +552,28 @@ impl<'a> TakeRandom for ListTakeRandom<'a> { #[inline] fn get(&self, index: usize) -> Option { let v = take_random_get!(self, index); - v.map(|v| { - let s = Series::try_from((self.ca.name(), v)); - s.unwrap() + v.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name, vec![arr], &self.inner_type) }) } #[inline] unsafe fn get_unchecked(&self, index: usize) -> Option { let v = take_random_get_unchecked!(self, index); - v.map(|v| { - let s = Series::try_from((self.ca.name(), v)); - s.unwrap() + v.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name, vec![arr], &self.inner_type) + }) + } + fn last(&self) -> Option { + self.chunks.last().and_then(|arr| { + let arr = arr.get(arr.len().saturating_sub(1)); + arr.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked( + self.name, + vec![arr.to_boxed()], + &self.inner_type, + ) + }) }) } } @@ -543,6 +605,9 @@ impl<'a> TakeRandom for ListTakeRandomSingleChunk<'a> { None } } + fn last(&self) -> Option { + self.get(self.arr.len().saturating_sub(1)) + } } #[cfg(feature = "object")] @@ -564,6 +629,11 @@ impl<'a, T: PolarsObject> TakeRandom for ObjectTakeRandom<'a, T> { unsafe fn get_unchecked(&self, index: usize) -> Option { take_random_get_unchecked!(self, index) } + fn last(&self) -> Option { + self.chunks + .last() + .and_then(|arr| arr.get(arr.len().saturating_sub(1))) + } } #[cfg(feature = "object")] @@ -588,6 +658,9 @@ impl<'a, T: PolarsObject> TakeRandom for ObjectTakeRandomSingleChunk<'a, T> { None } } + fn last(&self) -> Option { + self.arr.get(self.arr.len().saturating_sub(1)) + } } #[cfg(feature = "object")] diff --git a/crates/polars-core/src/chunked_array/ops/take/take_single.rs b/crates/polars-core/src/chunked_array/ops/take/take_single.rs index 162b095271b9..962dc2dd331b 100644 --- a/crates/polars-core/src/chunked_array/ops/take/take_single.rs +++ b/crates/polars-core/src/chunked_array/ops/take/take_single.rs @@ -1,5 +1,3 @@ -use std::convert::TryFrom; - use arrow::array::*; use polars_arrow::is_valid::IsValid; @@ -67,6 +65,11 @@ where unsafe fn get_unchecked(&self, index: usize) -> Option { impl_take_random_get_unchecked!(self, index, PrimitiveArray) } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl<'a, T> TakeRandom for &'a ChunkedArray @@ -84,6 +87,11 @@ where unsafe fn get_unchecked(&self, index: usize) -> Option { (*self).get_unchecked(index) } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl TakeRandom for BooleanChunked { @@ -99,6 +107,11 @@ impl TakeRandom for BooleanChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { impl_take_random_get_unchecked!(self, index, BooleanArray) } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl<'a> TakeRandom for &'a BooleanChunked { @@ -113,6 +126,11 @@ impl<'a> TakeRandom for &'a BooleanChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { (*self).get_unchecked(index) } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl<'a> TakeRandom for &'a Utf8Chunked { @@ -124,6 +142,11 @@ impl<'a> TakeRandom for &'a Utf8Chunked { // Out of bounds is checked and downcast is of correct type unsafe { impl_take_random_get!(self, index, LargeStringArray) } } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl<'a> TakeRandom for &'a BinaryChunked { @@ -135,6 +158,11 @@ impl<'a> TakeRandom for &'a BinaryChunked { // Out of bounds is checked and downcast is of correct type unsafe { impl_take_random_get!(self, index, LargeBinaryArray) } } + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } // extra trait such that it also works without extra reference. @@ -153,6 +181,12 @@ impl<'a> TakeRandomUtf8 for &'a Utf8Chunked { unsafe fn get_unchecked(self, index: usize) -> Option { impl_take_random_get_unchecked!(self, index, LargeStringArray) } + + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } #[cfg(feature = "object")] @@ -170,6 +204,12 @@ impl<'a, T: PolarsObject> TakeRandom for &'a ObjectChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { impl_take_random_get_unchecked!(self, index, ObjectArray) } + + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)) + } } impl TakeRandom for ListChunked { @@ -180,18 +220,24 @@ impl TakeRandom for ListChunked { // Safety: // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, LargeListArray) }; - opt_arr.map(|arr| { - let s = Series::try_from((self.name(), arr)); - s.unwrap() + opt_arr.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) }) } #[inline] unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, LargeListArray); - opt_arr.map(|arr| { - let s = Series::try_from((self.name(), arr)); - s.unwrap() + opt_arr.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + }) + } + + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) }) } } @@ -205,18 +251,24 @@ impl TakeRandom for ArrayChunked { // Safety: // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, FixedSizeListArray) }; - opt_arr.map(|arr| { - let s = Series::try_from((self.name(), arr)); - s.unwrap() + opt_arr.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) }) } #[inline] unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, FixedSizeListArray); - opt_arr.map(|arr| { - let s = Series::try_from((self.name(), arr)); - s.unwrap() + opt_arr.map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + }) + } + + fn last(&self) -> Option { + let chunks = self.downcast_chunks(); + let arr = chunks.get(chunks.len() - 1).unwrap(); + arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) }) } } From f587b2da26f9084a169683a3b767aae42425e2fd Mon Sep 17 00:00:00 2001 From: ritchie Date: Wed, 2 Aug 2023 12:17:44 +0200 Subject: [PATCH 2/3] use physical types --- .../src/chunked_array/ops/take/take_random.rs | 2 +- .../src/chunked_array/ops/take/take_single.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/polars-core/src/chunked_array/ops/take/take_random.rs b/crates/polars-core/src/chunked_array/ops/take/take_random.rs index 9248c02129bd..8f93ce84ea2b 100644 --- a/crates/polars-core/src/chunked_array/ops/take/take_random.rs +++ b/crates/polars-core/src/chunked_array/ops/take/take_random.rs @@ -363,7 +363,7 @@ impl<'a> IntoTakeRandom<'a> for &'a ListChunked { TakeRandBranch2::Single(t) } else { let name = self.name(); - let inner_type = self.inner_dtype(); + let inner_type = self.inner_dtype().to_physical(); let t = ListTakeRandom { name, inner_type, diff --git a/crates/polars-core/src/chunked_array/ops/take/take_single.rs b/crates/polars-core/src/chunked_array/ops/take/take_single.rs index 962dc2dd331b..a0060e33ccb3 100644 --- a/crates/polars-core/src/chunked_array/ops/take/take_single.rs +++ b/crates/polars-core/src/chunked_array/ops/take/take_single.rs @@ -221,7 +221,7 @@ impl TakeRandom for ListChunked { // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, LargeListArray) }; opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } @@ -229,7 +229,7 @@ impl TakeRandom for ListChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, LargeListArray); opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } @@ -237,7 +237,7 @@ impl TakeRandom for ListChunked { let chunks = self.downcast_chunks(); let arr = chunks.get(chunks.len() - 1).unwrap(); arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } } @@ -252,7 +252,7 @@ impl TakeRandom for ArrayChunked { // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, FixedSizeListArray) }; opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } @@ -260,7 +260,7 @@ impl TakeRandom for ArrayChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, FixedSizeListArray); opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } @@ -268,7 +268,7 @@ impl TakeRandom for ArrayChunked { let chunks = self.downcast_chunks(); let arr = chunks.get(chunks.len() - 1).unwrap(); arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype()) + Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) }) } } From ed528a5c36aec95530439e9898d6bef2d3207be4 Mon Sep 17 00:00:00 2001 From: ritchie Date: Wed, 2 Aug 2023 12:26:00 +0200 Subject: [PATCH 3/3] lint --- .../src/chunked_array/ops/take/take_single.rs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/polars-core/src/chunked_array/ops/take/take_single.rs b/crates/polars-core/src/chunked_array/ops/take/take_single.rs index a0060e33ccb3..dca4433cabb3 100644 --- a/crates/polars-core/src/chunked_array/ops/take/take_single.rs +++ b/crates/polars-core/src/chunked_array/ops/take/take_single.rs @@ -221,7 +221,11 @@ impl TakeRandom for ListChunked { // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, LargeListArray) }; opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } @@ -229,7 +233,11 @@ impl TakeRandom for ListChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, LargeListArray); opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } @@ -237,7 +245,11 @@ impl TakeRandom for ListChunked { let chunks = self.downcast_chunks(); let arr = chunks.get(chunks.len() - 1).unwrap(); arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } } @@ -252,7 +264,11 @@ impl TakeRandom for ArrayChunked { // Out of bounds is checked and downcast is of correct type let opt_arr = unsafe { impl_take_random_get!(self, index, FixedSizeListArray) }; opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } @@ -260,7 +276,11 @@ impl TakeRandom for ArrayChunked { unsafe fn get_unchecked(&self, index: usize) -> Option { let opt_arr = impl_take_random_get_unchecked!(self, index, FixedSizeListArray); opt_arr.map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } @@ -268,7 +288,11 @@ impl TakeRandom for ArrayChunked { let chunks = self.downcast_chunks(); let arr = chunks.get(chunks.len() - 1).unwrap(); arr.get(arr.len().saturating_sub(1)).map(|arr| unsafe { - Series::from_chunks_and_dtype_unchecked(self.name(), vec![arr], &self.inner_dtype().to_physical()) + Series::from_chunks_and_dtype_unchecked( + self.name(), + vec![arr], + &self.inner_dtype().to_physical(), + ) }) } }