1#![allow(unsafe_op_in_unsafe_fn)]
2use polars_arrow::array::*;
3use polars_arrow::bitmap::utils::set_bit_unchecked;
4use polars_arrow::bitmap::{Bitmap, MutableBitmap};
5use polars_arrow::legacy::array::SlicedArray;
6use polars_arrow::offset::OffsetsBuffer;
7
8use crate::prelude::*;
9use crate::series::implementations::null::NullChunked;
10
11pub(crate) trait ExplodeByOffsets {
12 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series;
13}
14
15unsafe fn unset_nulls(
16 start: usize,
17 last: usize,
18 validity_values: &Bitmap,
19 nulls: &mut Vec<usize>,
20 empty_row_idx: &[usize],
21 base_offset: usize,
22) {
23 for i in start..last {
24 if !validity_values.get_bit_unchecked(i) {
25 nulls.push(i + empty_row_idx.len() - base_offset);
26 }
27 }
28}
29
30fn get_capacity(offsets: &[i64]) -> usize {
31 (offsets[offsets.len() - 1] - offsets[0] + 1) as usize
32}
33
34impl<T> ExplodeByOffsets for ChunkedArray<T>
35where
36 T: PolarsIntegerType,
37{
38 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
39 debug_assert_eq!(self.chunks.len(), 1);
40 let arr = self.downcast_iter().next().unwrap();
41
42 let values = &arr.values().as_slice()[..offsets[offsets.len() - 1] as usize];
44
45 let mut empty_row_idx = vec![];
46 let mut nulls = vec![];
47
48 let mut start = offsets[0] as usize;
49 let base_offset = start;
50 let mut last = start;
51
52 let mut new_values = Vec::with_capacity(offsets[offsets.len() - 1] as usize - start + 1);
53
54 if arr.null_count() > 0 {
67 let validity_values = arr.validity().unwrap();
68
69 for &o in &offsets[1..] {
70 let o = o as usize;
71 if options.empty_as_null && o == last {
72 if start != last {
73 #[cfg(debug_assertions)]
74 new_values.extend_from_slice(&values[start..last]);
75
76 #[cfg(not(debug_assertions))]
77 unsafe {
78 new_values.extend_from_slice(values.get_unchecked(start..last))
79 };
80
81 unsafe {
84 unset_nulls(
85 start,
86 last,
87 validity_values,
88 &mut nulls,
89 &empty_row_idx,
90 base_offset,
91 )
92 }
93 }
94
95 empty_row_idx.push(o + empty_row_idx.len() - base_offset);
96 new_values.push(T::Native::default());
97 start = o;
98 }
99 last = o;
100 }
101
102 unsafe {
106 unset_nulls(
107 start,
108 last,
109 validity_values,
110 &mut nulls,
111 &empty_row_idx,
112 base_offset,
113 )
114 }
115 } else {
116 for &o in &offsets[1..] {
117 let o = o as usize;
118 if options.empty_as_null && o == last {
119 if start != last {
120 unsafe { new_values.extend_from_slice(values.get_unchecked(start..last)) };
121 }
122
123 empty_row_idx.push(o + empty_row_idx.len() - base_offset);
124 new_values.push(T::Native::default());
125 start = o;
126 }
127 last = o;
128 }
129 }
130
131 new_values.extend_from_slice(&values[start..]);
133
134 let mut validity = MutableBitmap::with_capacity(new_values.len());
135 validity.extend_constant(new_values.len(), true);
136 let validity_slice = validity.as_mut_slice();
137
138 for i in empty_row_idx {
139 unsafe { set_bit_unchecked(validity_slice, i, false) }
140 }
141 for i in nulls {
142 unsafe { set_bit_unchecked(validity_slice, i, false) }
143 }
144 let arr = PrimitiveArray::new(
145 T::get_static_dtype().to_arrow(CompatLevel::newest()),
146 new_values.into(),
147 Some(validity.into()),
148 );
149 Series::try_from((self.name().clone(), Box::new(arr) as ArrayRef)).unwrap()
150 }
151}
152
153#[cfg(feature = "dtype-f16")]
154impl ExplodeByOffsets for Float16Chunked {
155 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
156 self.apply_as_ints(|s| {
157 let ca = s.u16().unwrap();
158 ca.explode_by_offsets(offsets, options)
159 })
160 }
161}
162
163impl ExplodeByOffsets for Float32Chunked {
164 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
165 self.apply_as_ints(|s| {
166 let ca = s.u32().unwrap();
167 ca.explode_by_offsets(offsets, options)
168 })
169 }
170}
171impl ExplodeByOffsets for Float64Chunked {
172 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
173 self.apply_as_ints(|s| {
174 let ca = s.u64().unwrap();
175 ca.explode_by_offsets(offsets, options)
176 })
177 }
178}
179
180impl ExplodeByOffsets for NullChunked {
181 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
182 let mut last_offset = offsets[0];
183
184 let mut len = 0;
185 for &offset in &offsets[1..] {
186 len += std::cmp::max(offset - last_offset, i64::from(options.empty_as_null)) as usize;
189 last_offset = offset;
190 }
191 NullChunked::new(self.name.clone(), len).into_series()
192 }
193}
194
195impl ExplodeByOffsets for BooleanChunked {
196 fn explode_by_offsets(&self, offsets: &[i64], options: ExplodeOptions) -> Series {
197 debug_assert_eq!(self.chunks.len(), 1);
198 let arr = self.downcast_iter().next().unwrap();
199
200 let cap = get_capacity(offsets);
201 let mut builder = BooleanChunkedBuilder::new(self.name().clone(), cap);
202
203 let mut start = offsets[0] as usize;
204 let mut last = start;
205 for &o in &offsets[1..] {
206 let o = o as usize;
207 if options.empty_as_null && o == last {
208 if start != last {
209 let vals = arr.slice_typed(start, last - start);
210
211 if vals.null_count() == 0 {
212 builder
213 .array_builder
214 .extend_trusted_len_values(vals.values_iter())
215 } else {
216 builder.array_builder.extend_trusted_len(vals.into_iter());
217 }
218 }
219 builder.append_null();
220 start = o;
221 }
222 last = o;
223 }
224 let vals = arr.slice_typed(start, last - start);
225 if vals.null_count() == 0 {
226 builder
227 .array_builder
228 .extend_trusted_len_values(vals.values_iter())
229 } else {
230 builder.array_builder.extend_trusted_len(vals.into_iter());
231 }
232 builder.finish().into()
233 }
234}
235
236#[inline]
238fn exploded_len(start: i64, end: i64, is_valid: bool, options: ExplodeOptions) -> usize {
239 if start == end {
240 ((is_valid && options.empty_as_null) || (!is_valid && options.keep_nulls)) as usize
241 } else {
242 (end - start) as usize
243 }
244}
245
246pub fn explode_with_row_offsets(
248 s: &Series,
249 options: ExplodeOptions,
250) -> PolarsResult<(Series, OffsetsBuffer<i64>)> {
251 let s = s.rechunk();
252 let (out, offsets) = match s.dtype() {
253 DataType::List(_) => s.list()?.explode_and_offsets(options)?,
254 #[cfg(feature = "dtype-array")]
255 DataType::Array(_, width) => {
256 let (out, offsets) = s.array()?.explode_and_offsets(options)?;
257 let offsets = if *width == 0 {
259 polars_arrow::offset::Offsets::new_zeroed(s.len()).into()
260 } else {
261 offsets
262 };
263 (out, offsets)
264 },
265 dt => polars_bail!(opq = explode_with_row_offsets, dt),
266 };
267
268 let validity = s.rechunk_validity();
269 let mut row_offsets = Vec::with_capacity(offsets.len());
270 let mut length_so_far = 0i64;
271 row_offsets.push(length_so_far);
272 for (i, w) in offsets.as_slice().windows(2).enumerate() {
273 let is_valid = validity.as_ref().is_none_or(|v| v.get_bit(i));
274 length_so_far += exploded_len(w[0], w[1], is_valid, options) as i64;
275 row_offsets.push(length_so_far);
276 }
277 polars_ensure!(
278 length_so_far as usize == out.len(),
279 ComputeError: "explode produced {} rows, expected {}", out.len(), length_so_far
280 );
281
282 Ok((out, unsafe {
284 OffsetsBuffer::new_unchecked(row_offsets.into())
285 }))
286}
287
288pub(crate) fn offsets_to_indexes(
290 offsets: &[i64],
291 capacity: usize,
292 options: ExplodeOptions,
293 validity: Option<&Bitmap>,
294) -> Vec<IdxSize> {
295 if offsets.is_empty() {
296 return vec![];
297 }
298
299 let mut idx = Vec::with_capacity(capacity);
300
301 let mut last_idx = 0;
302 match validity {
303 None => {
304 for (offset_start, offset_end) in offsets.iter().zip(offsets[1..].iter()) {
305 if idx.len() >= capacity {
306 break;
309 }
310
311 let n = exploded_len(*offset_start, *offset_end, true, options);
312 idx.extend(std::iter::repeat_n(last_idx, n));
313 last_idx += 1;
314 }
315 },
316 Some(validity) => {
317 for ((offset_start, offset_end), is_valid) in
318 offsets.iter().zip(offsets[1..].iter()).zip(validity.iter())
319 {
320 if idx.len() >= capacity {
321 break;
324 }
325
326 let n = exploded_len(*offset_start, *offset_end, is_valid, options);
327 idx.extend(std::iter::repeat_n(last_idx, n));
328 last_idx += 1;
329 }
330 },
331 }
332
333 for _ in 0..capacity.saturating_sub(idx.len()) {
335 idx.push(last_idx);
336 }
337 idx.truncate(capacity);
338 idx
339}
340
341#[cfg(test)]
342mod test {
343 use super::*;
344 use crate::chunked_array::builder::get_list_builder;
345
346 #[test]
347 fn test_explode_list() -> PolarsResult<()> {
348 let mut builder = get_list_builder(&DataType::Int32, 5, 5, PlSmallStr::from_static("a"));
349
350 builder
351 .append_series(&Series::new(PlSmallStr::EMPTY, &[1, 2, 3, 3]))
352 .unwrap();
353 builder
354 .append_series(&Series::new(PlSmallStr::EMPTY, &[1]))
355 .unwrap();
356 builder
357 .append_series(&Series::new(PlSmallStr::EMPTY, &[2]))
358 .unwrap();
359
360 let ca = builder.finish();
361 assert!(ca._can_fast_explode());
362
363 let exploded = ca.explode(ExplodeOptions {
365 empty_as_null: true,
366 keep_nulls: true,
367 })?;
368 let out: Vec<_> = exploded.i32()?.into_no_null_iter().collect();
369 assert_eq!(out, &[1, 2, 3, 3, 1, 2]);
370
371 let exploded = ca.slice(0, 1).explode(ExplodeOptions {
373 empty_as_null: true,
374 keep_nulls: true,
375 })?;
376 let out: Vec<_> = exploded.i32()?.into_no_null_iter().collect();
377 assert_eq!(out, &[1, 2, 3, 3]);
378
379 Ok(())
380 }
381
382 #[test]
383 fn test_explode_empty_list_slot() -> PolarsResult<()> {
384 let mut builder = get_list_builder(&DataType::Int32, 5, 5, PlSmallStr::from_static("a"));
386 builder
387 .append_series(&Series::new(PlSmallStr::EMPTY, &[1i32, 2]))
388 .unwrap();
389 builder
390 .append_series(&Int32Chunked::from_slice(PlSmallStr::EMPTY, &[]).into_series())
391 .unwrap();
392 builder
393 .append_series(&Series::new(PlSmallStr::EMPTY, &[3i32]))
394 .unwrap();
395
396 let ca = builder.finish();
397 let exploded = ca.explode(ExplodeOptions {
398 empty_as_null: true,
399 keep_nulls: true,
400 })?;
401 assert_eq!(
402 Vec::from(exploded.i32()?),
403 &[Some(1), Some(2), None, Some(3)]
404 );
405
406 let mut builder = get_list_builder(&DataType::Int32, 5, 5, PlSmallStr::from_static("a"));
408 builder
409 .append_series(&Series::new(PlSmallStr::EMPTY, &[1i32]))
410 .unwrap();
411 builder
412 .append_series(&Int32Chunked::from_slice(PlSmallStr::EMPTY, &[]).into_series())
413 .unwrap();
414 builder
415 .append_series(&Series::new(PlSmallStr::EMPTY, &[2i32]))
416 .unwrap();
417 builder
418 .append_series(&Int32Chunked::from_slice(PlSmallStr::EMPTY, &[]).into_series())
419 .unwrap();
420 builder
421 .append_series(&Series::new(PlSmallStr::EMPTY, &[3, 4i32]))
422 .unwrap();
423
424 let ca = builder.finish();
425 let exploded = ca.explode(ExplodeOptions {
426 empty_as_null: true,
427 keep_nulls: true,
428 })?;
429 assert_eq!(
430 Vec::from(exploded.i32()?),
431 &[Some(1), None, Some(2), None, Some(3), Some(4)]
432 );
433
434 let mut builder = get_list_builder(&DataType::String, 5, 5, PlSmallStr::from_static("a"));
436 builder
437 .append_series(&Series::new(PlSmallStr::EMPTY, &["abc"]))
438 .unwrap();
439 builder
440 .append_series(
441 &<StringChunked as NewChunkedArray<StringType, &str>>::from_slice(
442 PlSmallStr::EMPTY,
443 &[],
444 )
445 .into_series(),
446 )
447 .unwrap();
448 builder
449 .append_series(&Series::new(PlSmallStr::EMPTY, &["de"]))
450 .unwrap();
451 builder
452 .append_series(
453 &<StringChunked as NewChunkedArray<StringType, &str>>::from_slice(
454 PlSmallStr::EMPTY,
455 &[],
456 )
457 .into_series(),
458 )
459 .unwrap();
460 builder
461 .append_series(&Series::new(PlSmallStr::EMPTY, &["fg"]))
462 .unwrap();
463 builder
464 .append_series(
465 &<StringChunked as NewChunkedArray<StringType, &str>>::from_slice(
466 PlSmallStr::EMPTY,
467 &[],
468 )
469 .into_series(),
470 )
471 .unwrap();
472
473 let ca = builder.finish();
474 let exploded = ca.explode(ExplodeOptions {
475 empty_as_null: true,
476 keep_nulls: true,
477 })?;
478 assert_eq!(
479 Vec::from(exploded.str()?),
480 &[Some("abc"), None, Some("de"), None, Some("fg"), None]
481 );
482
483 let mut builder = get_list_builder(&DataType::Boolean, 5, 5, PlSmallStr::from_static("a"));
485 builder
486 .append_series(&Series::new(PlSmallStr::EMPTY, &[true]))
487 .unwrap();
488 builder
489 .append_series(&BooleanChunked::from_slice(PlSmallStr::EMPTY, &[]).into_series())
490 .unwrap();
491 builder
492 .append_series(&Series::new(PlSmallStr::EMPTY, &[false]))
493 .unwrap();
494 builder
495 .append_series(&BooleanChunked::from_slice(PlSmallStr::EMPTY, &[]).into_series())
496 .unwrap();
497 builder
498 .append_series(&Series::new(PlSmallStr::EMPTY, &[true, true]))
499 .unwrap();
500
501 let ca = builder.finish();
502 let exploded = ca.explode(ExplodeOptions {
503 empty_as_null: true,
504 keep_nulls: true,
505 })?;
506 assert_eq!(
507 Vec::from(exploded.bool()?),
508 &[Some(true), None, Some(false), None, Some(true), Some(true)]
509 );
510
511 Ok(())
512 }
513
514 #[test]
515 fn test_row_offsets() {
516 let offsets = &[0, 1, 2, 2, 3, 4, 4];
517 let out = offsets_to_indexes(
518 offsets,
519 6,
520 ExplodeOptions {
521 empty_as_null: true,
522 keep_nulls: true,
523 },
524 None,
525 );
526 assert_eq!(out, &[0, 1, 2, 3, 4, 5]);
527 }
528
529 #[test]
530 fn test_empty_row_offsets() {
531 let offsets = &[0, 0];
532 let out = offsets_to_indexes(
533 offsets,
534 0,
535 ExplodeOptions {
536 empty_as_null: true,
537 keep_nulls: true,
538 },
539 None,
540 );
541 let expected: Vec<IdxSize> = Vec::new();
542 assert_eq!(out, expected);
543 }
544
545 #[test]
546 fn test_row_offsets_over_capacity() {
547 let offsets = &[0, 1, 1, 2, 2];
548 let out = offsets_to_indexes(
549 offsets,
550 2,
551 ExplodeOptions {
552 empty_as_null: true,
553 keep_nulls: true,
554 },
555 None,
556 );
557 assert_eq!(out, &[0, 1]);
558 }
559
560 #[test]
561 fn test_row_offsets_nonzero_first_offset() {
562 let offsets = &[3, 6, 8];
563 let out = offsets_to_indexes(
564 offsets,
565 10,
566 ExplodeOptions {
567 empty_as_null: true,
568 keep_nulls: true,
569 },
570 None,
571 );
572 assert_eq!(out, &[0, 0, 0, 1, 1, 2, 2, 2, 2, 2]);
573 }
574}