polars_core/chunked_array/ops/
chunkops.rs1use std::borrow::Cow;
2use std::cell::Cell;
3
4use polars_arrow::bitmap::{Bitmap, BitmapBuilder};
5use polars_arrow::compute::concatenate::concatenate_unchecked;
6use polars_error::constants::LENGTH_LIMIT_MSG;
7
8use super::*;
9use crate::chunked_array::flags::StatisticsFlags;
10#[cfg(feature = "object")]
11use crate::chunked_array::object::builder::ObjectChunkedBuilder;
12use crate::utils::slice_offsets;
13
14pub(crate) fn split_at(
15 chunks: &[ArrayRef],
16 offset: i64,
17 own_length: usize,
18) -> (Vec<ArrayRef>, Vec<ArrayRef>) {
19 let mut new_chunks_left = Vec::with_capacity(1);
20 let mut new_chunks_right = Vec::with_capacity(1);
21 let (raw_offset, _) = slice_offsets(offset, 0, own_length);
22
23 let mut remaining_offset = raw_offset;
24 let mut iter = chunks.iter().filter(|c| !c.is_empty());
25
26 for chunk in &mut iter {
27 let chunk_len = chunk.len();
28 if remaining_offset > 0 && remaining_offset >= chunk_len {
29 remaining_offset -= chunk_len;
30 new_chunks_left.push(chunk.clone());
31 continue;
32 }
33
34 let (l, r) = chunk.split_at_boxed(remaining_offset);
35 new_chunks_left.push(l);
36 new_chunks_right.push(r);
37 break;
38 }
39
40 for chunk in iter {
41 new_chunks_right.push(chunk.clone())
42 }
43 if new_chunks_left.is_empty() {
44 new_chunks_left.push(chunks[0].sliced(0, 0));
45 }
46 if new_chunks_right.is_empty() {
47 new_chunks_right.push(chunks[0].sliced(0, 0));
48 }
49 (new_chunks_left, new_chunks_right)
50}
51
52pub(crate) fn slice(
53 chunks: &[ArrayRef],
54 offset: i64,
55 slice_length: usize,
56 own_length: usize,
57) -> (Vec<ArrayRef>, usize) {
58 let mut new_chunks = Vec::with_capacity(1);
59 let (raw_offset, slice_len) = slice_offsets(offset, slice_length, own_length);
60
61 let mut remaining_length = slice_len;
62 let mut remaining_offset = raw_offset;
63 let mut new_len = 0;
64
65 for chunk in chunks {
66 let chunk_len = chunk.len();
67 if remaining_offset > 0 && remaining_offset >= chunk_len {
68 remaining_offset -= chunk_len;
69 continue;
70 }
71 let take_len = if remaining_length + remaining_offset > chunk_len {
72 chunk_len - remaining_offset
73 } else {
74 remaining_length
75 };
76 new_len += take_len;
77
78 debug_assert!(remaining_offset + take_len <= chunk.len());
79 unsafe {
80 new_chunks.push(chunk.sliced_unchecked(remaining_offset, take_len));
83 }
84 remaining_length -= take_len;
85 remaining_offset = 0;
86 if remaining_length == 0 {
87 break;
88 }
89 }
90 if new_chunks.is_empty() {
91 new_chunks.push(chunks[0].sliced(0, 0));
92 }
93 (new_chunks, new_len)
94}
95
96thread_local!(pub static CHECK_LENGTH: Cell<bool> = const { Cell::new(true) });
101
102pub unsafe fn _set_check_length(check: bool) {
107 CHECK_LENGTH.set(check)
108}
109
110impl<T: PolarsDataType> ChunkedArray<T> {
111 #[inline]
113 pub fn len(&self) -> usize {
114 self.length
115 }
116
117 #[inline]
119 pub fn null_count(&self) -> usize {
120 self.null_count
121 }
122
123 pub unsafe fn set_null_count(&mut self, null_count: usize) {
132 self.null_count = null_count;
133 }
134
135 #[inline]
137 pub fn is_empty(&self) -> bool {
138 self.len() == 0
139 }
140
141 pub(crate) fn compute_len(&mut self) {
143 fn inner(chunks: &[ArrayRef]) -> usize {
144 match chunks.len() {
145 1 => chunks[0].len(),
147 _ => chunks.iter().fold(0, |acc, arr| acc + arr.len()),
148 }
149 }
150 let len = inner(&self.chunks);
151 if len >= (IdxSize::MAX as usize) && CHECK_LENGTH.get() {
153 panic!("{}", LENGTH_LIMIT_MSG);
154 }
155 self.length = len;
156 self.null_count = self
157 .chunks
158 .iter()
159 .map(|arr| arr.null_count())
160 .sum::<usize>();
161 }
162
163 pub fn rechunk(&self) -> Cow<'_, Self> {
166 match self.dtype() {
167 #[cfg(feature = "object")]
168 DataType::Object(_) => {
169 panic!("implementation error")
170 },
171 _ => {
172 if self.chunks.len() == 1 {
173 Cow::Borrowed(self)
174 } else {
175 let chunks = vec![concatenate_unchecked(&self.chunks).unwrap()];
176
177 let mut ca = unsafe { self.copy_with_chunks(chunks) };
178 use StatisticsFlags as F;
179 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
180 Cow::Owned(ca)
181 }
182 },
183 }
184 }
185
186 pub fn rechunk_mut(&mut self) {
188 if self.chunks.len() > 1 {
189 let rechunked = concatenate_unchecked(&self.chunks).unwrap();
190 if self.chunks.capacity() <= 8 {
191 self.chunks.clear();
193 self.chunks.push(rechunked);
194 } else {
195 self.chunks = vec![rechunked];
196 }
197 }
198 }
199
200 pub fn rechunk_validity(&self) -> Option<Bitmap> {
201 if self.chunks.len() == 1 {
202 return self.chunks[0].validity().cloned();
203 }
204
205 if !self.has_nulls() || self.is_empty() {
206 return None;
207 }
208
209 let mut bm = BitmapBuilder::with_capacity(self.len());
210 for arr in self.downcast_iter() {
211 if let Some(v) = arr.validity() {
212 bm.extend_from_bitmap(v);
213 } else {
214 bm.extend_constant(arr.len(), true);
215 }
216 }
217 bm.into_opt_validity()
218 }
219
220 pub fn with_validities(&mut self, validities: &[Option<Bitmap>]) {
221 assert_eq!(validities.len(), self.chunks.len());
222
223 for (arr, validity) in unsafe { self.chunks_mut().iter_mut() }.zip(validities.iter()) {
226 *arr = arr.with_validity(validity.clone())
227 }
228 self.compute_len();
229 }
230
231 pub fn split_at(&self, offset: i64) -> (Self, Self) {
237 let (l, r) = split_at(&self.chunks, offset, self.len());
239 let mut out_l = unsafe { self.copy_with_chunks(l) };
240 let mut out_r = unsafe { self.copy_with_chunks(r) };
241
242 use StatisticsFlags as F;
243 out_l.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
244 out_r.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
245
246 (out_l, out_r)
247 }
248
249 pub fn slice(&self, offset: i64, length: usize) -> Self {
255 let exec = || {
258 let (chunks, len) = slice(&self.chunks, offset, length, self.len());
259 let mut out = unsafe { self.copy_with_chunks(chunks) };
260
261 use StatisticsFlags as F;
262 out.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
263 out.length = len;
264
265 out
266 };
267
268 match length {
269 0 => match self.dtype() {
270 #[cfg(feature = "object")]
271 DataType::Object(_) => exec(),
272 _ => self.clear(),
273 },
274 _ => exec(),
275 }
276 }
277
278 #[must_use]
280 pub fn limit(&self, num_elements: usize) -> Self
281 where
282 Self: Sized,
283 {
284 self.slice(0, num_elements)
285 }
286
287 #[must_use]
289 pub fn head(&self, length: Option<usize>) -> Self
290 where
291 Self: Sized,
292 {
293 match length {
294 Some(len) => self.slice(0, std::cmp::min(len, self.len())),
295 None => self.slice(0, std::cmp::min(10, self.len())),
296 }
297 }
298
299 #[must_use]
301 pub fn tail(&self, length: Option<usize>) -> Self
302 where
303 Self: Sized,
304 {
305 let len = match length {
306 Some(len) => std::cmp::min(len, self.len()),
307 None => std::cmp::min(10, self.len()),
308 };
309 self.slice(-(len as i64), len)
310 }
311
312 pub fn prune_empty_chunks(&mut self) {
314 let mut count = 0u32;
315 unsafe {
316 self.chunks_mut().retain(|arr| {
317 count += 1;
318 if count == 1 {
320 true
321 } else {
322 !arr.is_empty()
324 }
325 })
326 }
327 }
328}
329
330#[cfg(feature = "object")]
331impl<T: PolarsObject> ObjectChunked<T> {
332 pub(crate) fn rechunk_object(&self) -> Self {
333 if self.chunks.len() == 1 {
334 self.clone()
335 } else {
336 use crate::chunked_array::object::registry::run_with_gil;
337
338 run_with_gil(|| {
339 let mut builder = ObjectChunkedBuilder::new(self.name().clone(), self.len());
340 let chunks = self.downcast_iter();
341
342 if !self.has_nulls() {
345 for arr in chunks {
346 for idx in 0..arr.len() {
347 builder.append_value(arr.value(idx).clone())
348 }
349 }
350 } else {
351 for arr in chunks {
352 for idx in 0..arr.len() {
353 if arr.is_valid(idx) {
354 builder.append_value(arr.value(idx).clone())
355 } else {
356 builder.append_null()
357 }
358 }
359 }
360 }
361 builder.finish()
362 })
363 }
364 }
365}
366
367#[cfg(test)]
368mod test {
369 #[cfg(feature = "dtype-categorical")]
370 use crate::prelude::*;
371
372 #[test]
373 #[cfg(feature = "dtype-categorical")]
374 fn test_categorical_map_after_rechunk() {
375 let s = Series::new(PlSmallStr::EMPTY, &["foo", "bar", "spam"]);
376 let mut a = s
377 .cast(&DataType::from_categories(Categories::global()))
378 .unwrap();
379
380 a.append(&a.slice(0, 2)).unwrap();
381 let a = a.rechunk();
382 assert!(a.cat32().unwrap().get_mapping().num_cats_upper_bound() > 0);
383 }
384}