1#![allow(unsafe_op_in_unsafe_fn)]
3use std::borrow::Cow;
4use std::sync::Arc;
5
6use polars_arrow::array::*;
7use polars_arrow::bitmap::Bitmap;
8use polars_arrow::compute::concatenate::concatenate_unchecked;
9use polars_arrow::compute::utils::combine_validities_and;
10use polars_compute::filter::filter_with_bitmap;
11use polars_utils::broadcast::BroadcastLength;
12
13use crate::prelude::{ChunkTakeUnchecked, *};
14
15pub mod ops;
16#[macro_use]
17pub mod arithmetic;
18pub mod builder;
19pub mod cast;
20pub mod collect;
21pub mod comparison;
22pub mod flags;
23pub mod float;
24pub mod iterator;
25#[cfg(feature = "ndarray")]
26pub(crate) mod ndarray;
27
28pub mod arg_min_max;
29#[cfg(feature = "dtype-array")]
30pub(crate) mod array;
31mod binary;
32mod binary_offset;
33mod bitwise;
34#[cfg(feature = "object")]
35mod drop;
36mod from;
37mod from_iterator;
38pub mod from_iterator_par;
39pub(crate) mod list;
40pub(crate) mod logical;
41#[cfg(feature = "object")]
42pub mod object;
43#[cfg(feature = "random")]
44mod random;
45#[cfg(feature = "dtype-struct")]
46mod struct_;
47#[cfg(any(
48 feature = "temporal",
49 feature = "dtype-datetime",
50 feature = "dtype-date"
51))]
52pub mod temporal;
53mod to_vec;
54mod trusted_len;
55pub(crate) use arg_min_max::*;
56#[cfg(feature = "dtype-struct")]
57pub use struct_::StructChunked;
58
59use self::flags::{StatisticsFlags, StatisticsFlagsIM};
60use crate::series::IsSorted;
61use crate::utils::{first_non_null, first_null, last_non_null};
62
63pub type ChunkLenIter<'a> = std::iter::Map<std::slice::Iter<'a, ArrayRef>, fn(&ArrayRef) -> usize>;
64
65pub struct ChunkedArray<T: PolarsDataType> {
140 pub(crate) field: Arc<Field>,
141 pub(crate) chunks: Vec<ArrayRef>,
142
143 pub(crate) flags: StatisticsFlagsIM,
144
145 length: usize,
146 null_count: usize,
147 _pd: std::marker::PhantomData<T>,
148}
149
150impl<T: PolarsDataType> ChunkedArray<T> {
151 fn should_rechunk(&self) -> bool {
152 self.chunks.len() > 1 && self.chunks.len() > self.len() / 3
153 }
154
155 fn optional_rechunk(mut self) -> Self {
156 if self.should_rechunk() {
158 self.rechunk_mut()
159 }
160 self
161 }
162
163 pub(crate) fn as_any(&self) -> &dyn std::any::Any {
164 self
165 }
166
167 pub fn unpack_series_matching_type<'a>(
169 &self,
170 series: &'a Series,
171 ) -> PolarsResult<&'a ChunkedArray<T>> {
172 polars_ensure!(
173 self.dtype() == series.dtype(),
174 SchemaMismatch: "cannot unpack series of type `{}` into `{}`",
175 series.dtype(),
176 self.dtype(),
177 );
178
179 Ok(unsafe { self.unpack_series_matching_physical_type(series) })
181 }
182
183 fn new_with_compute_len(field: Arc<Field>, chunks: Vec<ArrayRef>) -> Self {
188 unsafe {
189 let mut chunked_arr = Self::new_with_dims(field, chunks, 0, 0);
190 chunked_arr.compute_len();
191 chunked_arr
192 }
193 }
194
195 pub unsafe fn new_with_dims(
199 field: Arc<Field>,
200 chunks: Vec<ArrayRef>,
201 length: usize,
202 null_count: usize,
203 ) -> Self {
204 Self {
205 field,
206 chunks,
207 flags: StatisticsFlagsIM::empty(),
208
209 _pd: Default::default(),
210 length,
211 null_count,
212 }
213 }
214
215 pub(crate) fn is_sorted_ascending_flag(&self) -> bool {
216 self.get_flags().is_sorted_ascending()
217 }
218
219 pub(crate) fn is_sorted_descending_flag(&self) -> bool {
220 self.get_flags().is_sorted_descending()
221 }
222
223 pub(crate) fn is_sorted_any(&self) -> bool {
225 self.get_flags().is_sorted_any()
226 }
227
228 pub fn unset_fast_explode_list(&mut self) {
229 self.set_fast_explode_list(false)
230 }
231
232 pub fn set_fast_explode_list(&mut self, value: bool) {
233 let mut flags = self.flags.get_mut();
234 flags.set(StatisticsFlags::CAN_FAST_EXPLODE_LIST, value);
235 self.flags.set_mut(flags);
236 }
237
238 pub fn get_fast_explode_list(&self) -> bool {
239 self.get_flags().can_fast_explode_list()
240 }
241
242 #[inline]
243 pub fn get_flags(&self) -> StatisticsFlags {
244 self.flags.get()
245 }
246
247 pub fn set_flags(&mut self, flags: StatisticsFlags) {
249 self.flags = StatisticsFlagsIM::new(flags);
250 }
251
252 pub fn is_sorted_flag(&self) -> IsSorted {
253 self.get_flags().is_sorted()
254 }
255
256 pub fn retain_flags_from<U: PolarsDataType>(
257 &mut self,
258 from: &ChunkedArray<U>,
259 retain_flags: StatisticsFlags,
260 ) {
261 let flags = from.flags.get();
262 if !flags.is_empty() {
264 self.set_flags(flags & retain_flags)
265 }
266 }
267
268 pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
270 let mut flags = self.flags.get_mut();
271 flags.set_sorted(sorted);
272 self.flags.set_mut(flags);
273 }
274
275 pub fn with_sorted_flag(&self, sorted: IsSorted) -> Self {
277 let mut out = self.clone();
278 out.set_sorted_flag(sorted);
279 out
280 }
281
282 pub fn first_null(&self) -> Option<usize> {
283 if self.null_count() == 0 {
284 None
285 }
286 else if self.null_count() == self.len() {
288 Some(0)
289 } else if self.is_sorted_any() {
290 let out = if self
291 .chunks
292 .iter()
293 .find(|arr| !arr.is_empty())
294 .unwrap()
295 .is_null(0)
296 {
297 0
299 } else {
300 self.len() - self.null_count()
302 };
303
304 debug_assert!(
305 unsafe { self.get_unchecked(out) }.is_none(),
307 "incorrect sorted flag"
308 );
309
310 Some(out)
311 } else {
312 first_null(self.chunks().iter().map(|arr| arr.as_ref()))
313 }
314 }
315
316 pub fn first_non_null(&self) -> Option<usize> {
318 if self.null_count() == self.len() {
319 None
320 }
321 else if self.null_count() == 0 {
323 Some(0)
324 } else if self.is_sorted_any() {
325 let out = if self
326 .chunks
327 .iter()
328 .find(|arr| !arr.is_empty())
329 .unwrap()
330 .is_null(0)
331 {
332 self.null_count()
334 } else {
335 0
337 };
338
339 debug_assert!(
340 unsafe { self.get_unchecked(out) }.is_some(),
342 "incorrect sorted flag"
343 );
344
345 Some(out)
346 } else {
347 first_non_null(self.chunks().iter().map(|arr| arr.as_ref()))
348 }
349 }
350
351 pub fn last_non_null(&self) -> Option<usize> {
353 if self.null_count() == self.len() {
354 None
355 }
356 else if self.null_count() == 0 {
358 Some(self.len() - 1)
359 } else if self.is_sorted_any() {
360 let out = if self
361 .chunks
362 .iter()
363 .find(|arr| !arr.is_empty())
364 .unwrap()
365 .is_null(0)
366 {
367 self.len() - 1
369 } else {
370 self.len() - self.null_count() - 1
372 };
373
374 debug_assert!(
375 unsafe { self.get_unchecked(out) }.is_some(),
377 "incorrect sorted flag"
378 );
379
380 Some(out)
381 } else {
382 last_non_null(self.chunks().iter().map(|arr| arr.as_ref()), self.len())
383 }
384 }
385
386 pub fn drop_nulls(&self) -> Self {
387 if self.null_count() == 0 {
388 self.clone()
389 } else {
390 let chunks = self
391 .downcast_iter()
392 .map(|arr| {
393 if arr.null_count() == 0 {
394 arr.to_boxed()
395 } else {
396 filter_with_bitmap(arr, arr.validity().unwrap())
397 }
398 })
399 .collect();
400 unsafe {
401 Self::new_with_dims(
402 self.field.clone(),
403 chunks,
404 self.len() - self.null_count(),
405 0,
406 )
407 }
408 }
409 }
410
411 #[inline]
413 #[allow(clippy::type_complexity)]
414 pub fn iter_validities(
415 &self,
416 ) -> impl ExactSizeIterator<Item = Option<&Bitmap>> + DoubleEndedIterator {
417 fn to_validity(arr: &ArrayRef) -> Option<&Bitmap> {
418 arr.validity()
419 }
420 self.chunks.iter().map(to_validity)
421 }
422
423 #[inline]
424 pub fn has_nulls(&self) -> bool {
426 self.null_count > 0
427 }
428
429 pub fn shrink_to_fit(&mut self) {
431 self.chunks = vec![concatenate_unchecked(self.chunks.as_slice()).unwrap()];
432 }
433
434 pub fn clear(&self) -> Self {
435 let mut ca = unsafe {
437 self.copy_with_chunks(vec![new_empty_array(
438 self.chunks.first().unwrap().dtype().clone(),
439 )])
440 };
441
442 use StatisticsFlags as F;
443 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
444 ca
445 }
446
447 pub(crate) unsafe fn unpack_series_matching_physical_type<'a>(
454 &self,
455 series: &'a Series,
456 ) -> &'a ChunkedArray<T> {
457 let series_trait = &**series;
458 if self.dtype() == series.dtype() {
459 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
460 } else {
461 use DataType::*;
462 match (self.dtype(), series.dtype()) {
463 (Int64, Datetime(_, _)) | (Int64, Duration(_)) | (Int32, Date) => {
464 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
465 },
466 _ => panic!(
467 "cannot unpack series {:?} into matching type {:?}",
468 series,
469 self.dtype()
470 ),
471 }
472 }
473 }
474
475 pub fn chunk_lengths(&self) -> ChunkLenIter<'_> {
477 self.chunks.iter().map(|chunk| chunk.len())
478 }
479
480 #[inline]
482 pub fn chunks(&self) -> &Vec<ArrayRef> {
483 &self.chunks
484 }
485
486 #[inline]
492 pub unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef> {
493 &mut self.chunks
494 }
495
496 unsafe fn copy_with_chunks(&self, chunks: Vec<ArrayRef>) -> Self {
501 Self::new_with_compute_len(self.field.clone(), chunks)
502 }
503
504 #[inline(always)]
506 pub fn dtype(&self) -> &DataType {
507 self.field.dtype()
508 }
509
510 pub(crate) unsafe fn set_dtype(&mut self, dtype: DataType) {
511 self.field = Arc::new(Field::new(self.name().clone(), dtype))
512 }
513
514 #[inline]
516 pub fn name(&self) -> &PlSmallStr {
517 self.field.name()
518 }
519
520 #[inline(always)]
522 pub fn ref_field(&self) -> &Field {
523 &self.field
524 }
525
526 pub fn rename(&mut self, name: PlSmallStr) {
528 self.field = Arc::new(Field::new(name, self.field.dtype().clone()));
529 }
530
531 pub fn with_name(mut self, name: PlSmallStr) -> Self {
533 self.rename(name);
534 self
535 }
536}
537
538impl<T> ChunkedArray<T>
539where
540 T: PolarsDataType,
541{
542 #[inline]
548 pub fn get(&self, idx: usize) -> Option<T::Physical<'_>> {
549 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
550 assert!(
551 chunk_idx < self.chunks().len(),
552 "index: {} out of bounds for len: {}",
553 idx,
554 self.len()
555 );
556 unsafe {
557 let arr = self.downcast_get_unchecked(chunk_idx);
558 assert!(
559 arr_idx < arr.len(),
560 "index: {} out of bounds for len: {}",
561 idx,
562 self.len()
563 );
564 arr.get_unchecked(arr_idx)
565 }
566 }
567
568 #[inline]
574 pub unsafe fn get_unchecked(&self, idx: usize) -> Option<T::Physical<'_>> {
575 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
576
577 unsafe {
578 self.downcast_get_unchecked(chunk_idx)
580 .get_unchecked(arr_idx)
581 }
582 }
583
584 #[inline]
590 pub unsafe fn value_unchecked(&self, idx: usize) -> T::Physical<'_> {
591 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
592
593 unsafe {
594 self.downcast_get_unchecked(chunk_idx)
596 .value_unchecked(arr_idx)
597 }
598 }
599
600 #[inline]
603 pub fn first(&self) -> Option<T::Physical<'_>> {
604 self.iter().next().unwrap()
605 }
606
607 #[inline]
610 pub fn last(&self) -> Option<T::Physical<'_>> {
611 let arr = self
612 .downcast_iter()
613 .rev()
614 .find(|arr| !arr.is_empty())
615 .unwrap();
616 unsafe { arr.get_unchecked(arr.len() - 1) }
617 }
618
619 pub fn set_validity(&mut self, validity: Option<Bitmap>) {
620 assert!(
621 !self.dtype().is_struct(),
622 "set_outer_validity should be used for struct types"
623 );
624 if let Some(v) = &validity {
625 assert_eq!(self.len(), v.len());
626 }
627 let mut i = 0;
628 for chunk in unsafe { self.chunks_mut() } {
629 *chunk =
630 chunk.with_validity(validity.as_ref().map(|v| v.clone().sliced(i, chunk.len())));
631 i += chunk.len();
632 }
633 self.null_count = validity.map(|v| v.unset_bits()).unwrap_or(0);
634 self.set_fast_explode_list(false);
635 }
636
637 pub fn with_validity(mut self, validity: Option<Bitmap>) -> Self {
638 self.set_validity(validity);
639 self
640 }
641}
642
643impl<T> ChunkedArray<T>
644where
645 T: PolarsDataType,
646 ChunkedArray<T>: ChunkExpandAtIndex<T>,
647{
648 pub fn broadcast_to(&self, length: usize) -> PolarsResult<Cow<'_, Self>> {
652 let len = self.len();
653 if len == length {
654 Ok(Cow::Borrowed(self))
655 } else if len == 1 {
656 Ok(Cow::Owned(self.new_from_index(0, length)))
657 } else {
658 polars_bail!(
659 ShapeMismatch: "can't broadcast Series '{}' of length {len} to length {length}",
660 self.name()
661 );
662 }
663 }
664
665 pub fn broadcast_in_place_to(&mut self, length: usize) -> PolarsResult<()> {
667 if let Cow::Owned(new) = self.broadcast_to(length)? {
668 *self = new;
669 }
670 Ok(())
671 }
672
673 pub fn broadcast_owned_to(mut self, length: usize) -> PolarsResult<Self> {
675 self.broadcast_in_place_to(length)?;
676 Ok(self)
677 }
678}
679
680impl<T> ChunkedArray<T>
681where
682 T: PolarsDataType,
683 ChunkedArray<T>: ChunkTakeUnchecked<[IdxSize]>,
684{
685 pub fn deposit(&self, validity: &Bitmap) -> Self {
687 let set_bits = validity.set_bits();
688
689 assert_eq!(self.len(), set_bits);
690
691 if set_bits == validity.len() {
692 return self.clone();
693 }
694
695 if set_bits == 0 {
696 return Self::full_null_like(self, validity.len());
697 }
698
699 let mut null_mask = validity.clone();
700
701 let mut gather_idxs = Vec::with_capacity(validity.len());
702 let leading_nulls = null_mask.take_leading_zeros();
703 gather_idxs.extend(std::iter::repeat_n(0, leading_nulls + 1));
704
705 let mut i = 0 as IdxSize;
706 gather_idxs.extend(null_mask.iter().skip(1).map(|v| {
707 i += IdxSize::from(v);
708 i
709 }));
710
711 let mut ca = unsafe { ChunkTakeUnchecked::take_unchecked(self, &gather_idxs) };
712 ca.set_validity(combine_validities_and(
713 Some(validity),
714 ca.rechunk_validity().as_ref(),
715 ));
716 ca
717 }
718}
719
720impl ListChunked {
721 #[inline]
722 pub fn get_as_series(&self, idx: usize) -> Option<Series> {
723 unsafe {
724 Some(Series::from_chunks_and_dtype_unchecked(
725 self.name().clone(),
726 vec![self.get(idx)?],
727 &self.inner_dtype().to_physical(),
728 ))
729 }
730 }
731
732 pub fn has_empty_lists(&self) -> bool {
733 for arr in self.downcast_iter() {
734 if arr.is_empty() {
735 continue;
736 }
737
738 if match arr.validity() {
739 None => arr.offsets().lengths().any(|l| l == 0),
740 Some(validity) => arr
741 .offsets()
742 .lengths()
743 .enumerate()
744 .any(|(i, l)| l == 0 && unsafe { validity.get_bit_unchecked(i) }),
745 } {
746 return true;
747 }
748 }
749
750 false
751 }
752
753 pub fn has_masked_out_values(&self) -> bool {
754 for arr in self.downcast_iter() {
755 if arr.is_empty() {
756 continue;
757 }
758
759 if *arr.offsets().first() != 0 || *arr.offsets().last() != arr.values().len() as i64 {
760 return true;
761 }
762
763 let Some(validity) = arr.validity() else {
764 continue;
765 };
766 if validity.set_bits() == 0 {
767 continue;
768 }
769
770 for i in (!validity).true_idx_iter() {
772 if arr.offsets().length_at(i) > 0 {
773 return true;
774 }
775 }
776 }
777
778 false
779 }
780}
781
782#[cfg(feature = "dtype-array")]
783impl ArrayChunked {
784 #[inline]
785 pub fn get_as_series(&self, idx: usize) -> Option<Series> {
786 unsafe {
787 Some(Series::from_chunks_and_dtype_unchecked(
788 self.name().clone(),
789 vec![self.get(idx)?],
790 &self.inner_dtype().to_physical(),
791 ))
792 }
793 }
794
795 pub fn from_aligned_values(
796 name: PlSmallStr,
797 inner_dtype: &DataType,
798 width: usize,
799 chunks: Vec<ArrayRef>,
800 length: usize,
801 ) -> Self {
802 let dtype = DataType::Array(Box::new(inner_dtype.clone()), width);
803 let arrow_dtype = inner_dtype
804 .to_physical()
805 .to_arrow(CompatLevel::newest())
806 .to_fixed_size_list(width, true);
807 let field = Arc::new(Field::new(name, dtype));
808 if width == 0 {
809 use polars_arrow::array::builder::{ArrayBuilder, make_builder};
810 let values = make_builder(&inner_dtype.to_arrow(CompatLevel::newest())).freeze();
811 return ArrayChunked::new_with_compute_len(
812 field,
813 vec![FixedSizeListArray::new(arrow_dtype, length, values, None).into_boxed()],
814 );
815 }
816 let mut total_len = 0;
817 let chunks = chunks
818 .iter()
819 .map(|chunk| {
820 debug_assert_eq!(chunk.len() % width, 0);
821 let chunk_len = chunk.len() / width;
822 total_len += chunk_len;
823 FixedSizeListArray::new(arrow_dtype.clone(), chunk_len, chunk.clone(), None)
824 .into_boxed()
825 })
826 .collect();
827 debug_assert_eq!(total_len, length);
828
829 unsafe { Self::new_with_dims(field, chunks, length, 0) }
830 }
831
832 pub fn to_list(&self) -> ListChunked {
836 let inner_dtype = self.inner_dtype();
837 let chunks = self
838 .downcast_iter()
839 .map(|chunk| {
840 use polars_arrow::offset::OffsetsBuffer;
841
842 let inner_dtype = chunk.dtype().inner_dtype().unwrap();
843 let dtype = inner_dtype.clone().to_large_list(true);
844
845 let offsets = (0..=chunk.len())
846 .map(|i| (i * self.width()) as i64)
847 .collect::<Vec<i64>>();
848
849 let offsets = unsafe { OffsetsBuffer::new_unchecked(offsets.into()) };
851
852 ListArray::<i64>::new(
853 dtype,
854 offsets,
855 chunk.values().clone(),
856 chunk.validity().cloned(),
857 )
858 .into_boxed()
859 })
860 .collect();
861
862 let mut ca = unsafe {
864 ListChunked::new_with_dims(
865 Arc::new(Field::new(
866 self.name().clone(),
867 DataType::List(Box::new(inner_dtype.clone())),
868 )),
869 chunks,
870 self.len(),
871 self.null_count(),
872 )
873 };
874 ca.set_fast_explode_list(!self.has_nulls());
875 ca
876 }
877}
878
879impl<T> ChunkedArray<T>
880where
881 T: PolarsDataType,
882{
883 pub fn match_chunks<I>(&self, chunk_id: I) -> Self
887 where
888 I: Iterator<Item = usize>,
889 {
890 debug_assert!(self.chunks.len() == 1);
891 let slice = |ca: &Self| {
893 let array = &ca.chunks[0];
894
895 let mut offset = 0;
896 let chunks = chunk_id
897 .map(|len| {
898 debug_assert!((offset + len) <= array.len());
900 let out = unsafe { array.sliced_unchecked(offset, len) };
901 offset += len;
902 out
903 })
904 .collect();
905
906 debug_assert_eq!(offset, array.len());
907
908 unsafe {
910 Self::from_chunks_and_dtype(self.name().clone(), chunks, self.dtype().clone())
911 }
912 };
913
914 if self.chunks.len() != 1 {
915 let out = self.rechunk();
916 slice(&out)
917 } else {
918 slice(self)
919 }
920 }
921}
922
923impl<T: PolarsDataType> AsRefDataType for ChunkedArray<T> {
924 fn as_ref_dtype(&self) -> &DataType {
925 self.dtype()
926 }
927}
928
929pub(crate) trait AsSinglePtr: AsRefDataType {
930 fn as_single_ptr(&mut self) -> PolarsResult<usize> {
932 polars_bail!(opq = as_single_ptr, self.as_ref_dtype());
933 }
934}
935
936impl<T> AsSinglePtr for ChunkedArray<T>
937where
938 T: PolarsNumericType,
939{
940 fn as_single_ptr(&mut self) -> PolarsResult<usize> {
941 self.rechunk_mut();
942 let a = self.data_views().next().unwrap();
943 let ptr = a.as_ptr();
944 Ok(ptr as usize)
945 }
946}
947
948impl AsSinglePtr for BooleanChunked {}
949impl AsSinglePtr for ListChunked {}
950#[cfg(feature = "dtype-array")]
951impl AsSinglePtr for ArrayChunked {}
952impl AsSinglePtr for StringChunked {}
953impl AsSinglePtr for BinaryChunked {}
954#[cfg(feature = "object")]
955impl<T: PolarsObject> AsSinglePtr for ObjectChunked<T> {}
956
957pub enum ChunkedArrayLayout<'a, T: PolarsDataType> {
958 SingleNoNull(&'a T::Array),
959 Single(&'a T::Array),
960 MultiNoNull(&'a ChunkedArray<T>),
961 Multi(&'a ChunkedArray<T>),
962}
963
964impl<T> ChunkedArray<T>
965where
966 T: PolarsDataType,
967{
968 pub fn layout(&self) -> ChunkedArrayLayout<'_, T> {
969 if self.chunks.len() == 1 {
970 let arr = self.downcast_iter().next().unwrap();
971 return if arr.null_count() == 0 {
972 ChunkedArrayLayout::SingleNoNull(arr)
973 } else {
974 ChunkedArrayLayout::Single(arr)
975 };
976 }
977
978 if self.downcast_iter().all(|a| a.null_count() == 0) {
979 ChunkedArrayLayout::MultiNoNull(self)
980 } else {
981 ChunkedArrayLayout::Multi(self)
982 }
983 }
984}
985
986impl<T> ChunkedArray<T>
987where
988 T: PolarsNumericType,
989{
990 pub fn cont_slice(&self) -> PolarsResult<&[T::Native]> {
992 polars_ensure!(
993 self.chunks.len() == 1 && self.chunks[0].null_count() == 0,
994 ComputeError: "chunked array is not contiguous"
995 );
996 Ok(self.downcast_iter().next().map(|arr| arr.values()).unwrap())
997 }
998
999 pub(crate) fn cont_slice_mut(&mut self) -> Option<&mut [T::Native]> {
1001 if self.chunks.len() == 1 && self.chunks[0].null_count() == 0 {
1002 let arr = unsafe { self.downcast_iter_mut().next().unwrap() };
1004 arr.get_mut_values()
1005 } else {
1006 None
1007 }
1008 }
1009
1010 pub fn data_views(&self) -> impl DoubleEndedIterator<Item = &[T::Native]> {
1014 self.downcast_iter().map(|arr| arr.values().as_slice())
1015 }
1016
1017 #[allow(clippy::wrong_self_convention)]
1018 pub fn into_no_null_iter(
1019 &self,
1020 ) -> impl '_ + Send + Sync + ExactSizeIterator<Item = T::Native> + DoubleEndedIterator + TrustedLen
1021 {
1022 #[allow(clippy::map_clone)]
1024 unsafe {
1026 self.data_views()
1027 .flatten()
1028 .map(|v| *v)
1029 .trust_my_length(self.len())
1030 }
1031 }
1032}
1033
1034impl<T: PolarsDataType> Clone for ChunkedArray<T> {
1035 fn clone(&self) -> Self {
1036 ChunkedArray {
1037 field: self.field.clone(),
1038 chunks: self.chunks.clone(),
1039 flags: self.flags.clone(),
1040
1041 _pd: Default::default(),
1042 length: self.length,
1043 null_count: self.null_count,
1044 }
1045 }
1046}
1047
1048impl<T: PolarsDataType> AsRef<ChunkedArray<T>> for ChunkedArray<T> {
1049 fn as_ref(&self) -> &ChunkedArray<T> {
1050 self
1051 }
1052}
1053
1054impl ValueSize for ListChunked {
1055 fn get_values_size(&self) -> usize {
1056 self.chunks
1057 .iter()
1058 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1059 }
1060}
1061
1062#[cfg(feature = "dtype-array")]
1063impl ValueSize for ArrayChunked {
1064 fn get_values_size(&self) -> usize {
1065 self.chunks
1066 .iter()
1067 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1068 }
1069}
1070impl ValueSize for StringChunked {
1071 fn get_values_size(&self) -> usize {
1072 self.chunks
1073 .iter()
1074 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1075 }
1076}
1077
1078impl ValueSize for BinaryOffsetChunked {
1079 fn get_values_size(&self) -> usize {
1080 self.chunks
1081 .iter()
1082 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1083 }
1084}
1085
1086pub(crate) fn align_inner_chunks(
1091 chunk_lens: impl Iterator<Item = usize>,
1092 values: &Series,
1093) -> Series {
1094 let chunk_lens = chunk_lens.collect::<Vec<_>>();
1095
1096 if chunk_lens.len() == values.chunks().len()
1097 && chunk_lens
1098 .iter()
1099 .zip(values.chunks())
1100 .all(|(len, arr)| *len == arr.len())
1101 {
1102 return values.clone();
1103 }
1104
1105 let mut values = values.rechunk();
1106 let chunks = unsafe { values.chunks_mut() };
1107 let mut arr = chunks.pop().unwrap();
1108 chunks.extend(chunk_lens.into_iter().map(|len| {
1109 let chunk;
1110 (chunk, arr) = arr.split_at_boxed(len);
1111 chunk
1112 }));
1113 assert!(arr.is_empty());
1114 values
1115}
1116
1117pub(crate) fn to_primitive<T: PolarsNumericType>(
1118 values: Vec<T::Native>,
1119 validity: Option<Bitmap>,
1120) -> PrimitiveArray<T::Native> {
1121 PrimitiveArray::new(
1122 T::get_static_dtype().to_arrow(CompatLevel::newest()),
1123 values.into(),
1124 validity,
1125 )
1126}
1127
1128pub(crate) fn to_array<T: PolarsNumericType>(
1129 values: Vec<T::Native>,
1130 validity: Option<Bitmap>,
1131) -> ArrayRef {
1132 Box::new(to_primitive::<T>(values, validity))
1133}
1134
1135impl<T: PolarsDataType> Default for ChunkedArray<T> {
1136 fn default() -> Self {
1137 let dtype = T::get_static_dtype();
1138 let arrow_dtype = dtype.to_physical().to_arrow(CompatLevel::newest());
1139 ChunkedArray {
1140 field: Arc::new(Field::new(PlSmallStr::EMPTY, dtype)),
1141 chunks: vec![new_empty_array(arrow_dtype)],
1143 flags: StatisticsFlagsIM::empty(),
1144
1145 _pd: Default::default(),
1146 length: 0,
1147 null_count: 0,
1148 }
1149 }
1150}
1151
1152impl<T: PolarsDataType> BroadcastLength for ChunkedArray<T> {
1153 fn _broadcast_len(&self) -> usize {
1154 self.len()
1155 }
1156
1157 fn _column_name(&self) -> Option<&str> {
1158 Some(self.name())
1159 }
1160}
1161
1162#[cfg(test)]
1163pub(crate) mod test {
1164 use crate::prelude::*;
1165
1166 pub(crate) fn get_chunked_array() -> Int32Chunked {
1167 ChunkedArray::new(PlSmallStr::from_static("a"), &[1, 2, 3])
1168 }
1169
1170 #[test]
1171 fn test_sort() {
1172 let a = Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 9, 3, 2]);
1173 let b = a
1174 .sort(false)
1175 .iter()
1176 .map(|opt| opt.unwrap())
1177 .collect::<Vec<_>>();
1178 assert_eq!(b, [1, 2, 3, 9]);
1179 let a = StringChunked::new(PlSmallStr::from_static("a"), &["b", "a", "c"]);
1180 let a = a.sort(false);
1181 let b = a.iter().collect::<Vec<_>>();
1182 assert_eq!(b, [Some("a"), Some("b"), Some("c")]);
1183 assert!(a.is_sorted_ascending_flag());
1184 }
1185
1186 #[test]
1187 fn arithmetic() {
1188 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 6, 40]);
1189 let b = &Int32Chunked::new(PlSmallStr::from_static("b"), &[-1, 2, 3, 4]);
1190
1191 println!("{:?}", a + b);
1194 println!("{:?}", a - b);
1195 println!("{:?}", a * b);
1196 println!("{:?}", a / b);
1197 }
1198
1199 #[test]
1200 fn iter() {
1201 let s1 = get_chunked_array();
1202 assert_eq!(s1.iter().fold(0, |acc, val| { acc + val.unwrap() }), 6)
1204 }
1205
1206 #[test]
1207 fn limit() {
1208 let a = get_chunked_array();
1209 let b = a.limit(2);
1210 println!("{b:?}");
1211 assert_eq!(b.len(), 2)
1212 }
1213
1214 #[test]
1215 fn filter() {
1216 let a = get_chunked_array();
1217 let b = a
1218 .filter(&BooleanChunked::new(
1219 PlSmallStr::from_static("filter"),
1220 &[true, false, false],
1221 ))
1222 .unwrap();
1223 assert_eq!(b.len(), 1);
1224 assert_eq!(b.iter().next(), Some(Some(1)));
1225 }
1226
1227 #[test]
1228 fn aggregates() {
1229 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 10, 9]);
1230 assert_eq!(a.max(), Some(100));
1231 assert_eq!(a.min(), Some(1));
1232 assert_eq!(a.sum(), Some(120))
1233 }
1234
1235 #[test]
1236 fn take() {
1237 let a = get_chunked_array();
1238 let new = a.take(&[0 as IdxSize, 1]).unwrap();
1239 assert_eq!(new.len(), 2)
1240 }
1241
1242 #[test]
1243 fn cast() {
1244 let a = get_chunked_array();
1245 let b = a.cast(&DataType::Int64).unwrap();
1246 assert_eq!(b.dtype(), &DataType::Int64)
1247 }
1248
1249 fn assert_slice_equal<T>(ca: &ChunkedArray<T>, eq: &[T::Native])
1250 where
1251 T: PolarsNumericType,
1252 {
1253 assert_eq!(ca.iter().map(|opt| opt.unwrap()).collect::<Vec<_>>(), eq)
1254 }
1255
1256 #[test]
1257 fn slice() {
1258 let mut first = UInt32Chunked::new(PlSmallStr::from_static("first"), &[0, 1, 2]);
1259 let second = UInt32Chunked::new(PlSmallStr::from_static("second"), &[3, 4, 5]);
1260 first.append(&second).unwrap();
1261 assert_slice_equal(&first.slice(0, 3), &[0, 1, 2]);
1262 assert_slice_equal(&first.slice(0, 4), &[0, 1, 2, 3]);
1263 assert_slice_equal(&first.slice(1, 4), &[1, 2, 3, 4]);
1264 assert_slice_equal(&first.slice(3, 2), &[3, 4]);
1265 assert_slice_equal(&first.slice(3, 3), &[3, 4, 5]);
1266 assert_slice_equal(&first.slice(-3, 3), &[3, 4, 5]);
1267 assert_slice_equal(&first.slice(-6, 6), &[0, 1, 2, 3, 4, 5]);
1268
1269 assert_eq!(first.slice(-7, 2).len(), 1);
1270 assert_eq!(first.slice(-3, 4).len(), 3);
1271 assert_eq!(first.slice(3, 4).len(), 3);
1272 assert_eq!(first.slice(10, 4).len(), 0);
1273 }
1274
1275 #[test]
1276 fn sorting() {
1277 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[9, 2, 4]);
1278 let sorted = s.sort(false);
1279 assert_slice_equal(&sorted, &[2, 4, 9]);
1280 let sorted = s.sort(true);
1281 assert_slice_equal(&sorted, &[9, 4, 2]);
1282
1283 let s: StringChunked = ["b", "a", "z"].iter().collect();
1284 let sorted = s.sort(false);
1285 assert_eq!(
1286 sorted.iter().collect::<Vec<_>>(),
1287 &[Some("a"), Some("b"), Some("z")]
1288 );
1289 let sorted = s.sort(true);
1290 assert_eq!(
1291 sorted.iter().collect::<Vec<_>>(),
1292 &[Some("z"), Some("b"), Some("a")]
1293 );
1294 let s: StringChunked = [Some("b"), None, Some("z")].iter().copied().collect();
1295 let sorted = s.sort(false);
1296 assert_eq!(
1297 sorted.iter().collect::<Vec<_>>(),
1298 &[None, Some("b"), Some("z")]
1299 );
1300 }
1301
1302 #[test]
1303 fn reverse() {
1304 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[1, 2, 3]);
1305 assert_slice_equal(&s.reverse(), &[3, 2, 1]);
1307 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[Some(1), None, Some(3)]);
1309 assert_eq!(Vec::from(&s.reverse()), &[Some(3), None, Some(1)]);
1310 let s = BooleanChunked::new(PlSmallStr::EMPTY, &[true, false]);
1311 assert_eq!(Vec::from(&s.reverse()), &[Some(false), Some(true)]);
1312
1313 let s = StringChunked::new(PlSmallStr::EMPTY, &["a", "b", "c"]);
1314 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), Some("b"), Some("a")]);
1315
1316 let s = StringChunked::new(PlSmallStr::EMPTY, &[Some("a"), None, Some("c")]);
1317 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), None, Some("a")]);
1318 }
1319
1320 #[test]
1321 #[cfg(feature = "dtype-categorical")]
1322 fn test_iter_categorical() {
1323 let ca = StringChunked::new(
1324 PlSmallStr::EMPTY,
1325 &[Some("foo"), None, Some("bar"), Some("ham")],
1326 );
1327 let cats = Categories::new(
1328 PlSmallStr::EMPTY,
1329 PlSmallStr::EMPTY,
1330 CategoricalPhysical::U32,
1331 );
1332 let ca = ca.cast(&DataType::from_categories(cats)).unwrap();
1333 let ca = ca.cat32().unwrap();
1334 let v: Vec<_> = ca.physical().iter().collect();
1335 assert_eq!(v, &[Some(0), None, Some(1), Some(2)]);
1336 }
1337
1338 #[test]
1339 #[ignore]
1340 fn test_shrink_to_fit() {
1341 let mut builder = StringChunkedBuilder::new(PlSmallStr::from_static("foo"), 2048);
1342 builder.append_value("foo");
1343 let mut arr = builder.finish();
1344 let before = arr
1345 .chunks()
1346 .iter()
1347 .map(|arr| polars_arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1348 .sum::<usize>();
1349 arr.shrink_to_fit();
1350 let after = arr
1351 .chunks()
1352 .iter()
1353 .map(|arr| polars_arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1354 .sum::<usize>();
1355 assert!(before > after);
1356 }
1357}