1#![allow(unsafe_op_in_unsafe_fn)]
3use std::borrow::Cow;
4use std::sync::Arc;
5
6use arrow::array::*;
7use arrow::bitmap::Bitmap;
8use arrow::compute::concatenate::concatenate_unchecked;
9use 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 pub fn get_flags(&self) -> StatisticsFlags {
243 self.flags.get()
244 }
245
246 pub fn set_flags(&mut self, flags: StatisticsFlags) {
248 self.flags = StatisticsFlagsIM::new(flags);
249 }
250
251 pub fn is_sorted_flag(&self) -> IsSorted {
252 self.get_flags().is_sorted()
253 }
254
255 pub fn retain_flags_from<U: PolarsDataType>(
256 &mut self,
257 from: &ChunkedArray<U>,
258 retain_flags: StatisticsFlags,
259 ) {
260 let flags = from.flags.get();
261 if !flags.is_empty() {
263 self.set_flags(flags & retain_flags)
264 }
265 }
266
267 pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
269 let mut flags = self.flags.get_mut();
270 flags.set_sorted(sorted);
271 self.flags.set_mut(flags);
272 }
273
274 pub fn with_sorted_flag(&self, sorted: IsSorted) -> Self {
276 let mut out = self.clone();
277 out.set_sorted_flag(sorted);
278 out
279 }
280
281 pub fn first_null(&self) -> Option<usize> {
282 if self.null_count() == 0 {
283 None
284 }
285 else if self.null_count() == self.len() {
287 Some(0)
288 } else if self.is_sorted_any() {
289 let out = if self
290 .chunks
291 .iter()
292 .find(|arr| !arr.is_empty())
293 .unwrap()
294 .is_null(0)
295 {
296 0
298 } else {
299 self.null_count()
301 };
302
303 debug_assert!(
304 unsafe { self.get_unchecked(out) }.is_some(),
306 "incorrect sorted flag"
307 );
308
309 Some(out)
310 } else {
311 first_null(self.chunks().iter().map(|arr| arr.as_ref()))
312 }
313 }
314
315 pub fn first_non_null(&self) -> Option<usize> {
317 if self.null_count() == self.len() {
318 None
319 }
320 else if self.null_count() == 0 {
322 Some(0)
323 } else if self.is_sorted_any() {
324 let out = if self
325 .chunks
326 .iter()
327 .find(|arr| !arr.is_empty())
328 .unwrap()
329 .is_null(0)
330 {
331 self.null_count()
333 } else {
334 0
336 };
337
338 debug_assert!(
339 unsafe { self.get_unchecked(out) }.is_some(),
341 "incorrect sorted flag"
342 );
343
344 Some(out)
345 } else {
346 first_non_null(self.chunks().iter().map(|arr| arr.as_ref()))
347 }
348 }
349
350 pub fn last_non_null(&self) -> Option<usize> {
352 if self.null_count() == self.len() {
353 None
354 }
355 else if self.null_count() == 0 {
357 Some(self.len() - 1)
358 } else if self.is_sorted_any() {
359 let out = if self
360 .chunks
361 .iter()
362 .find(|arr| !arr.is_empty())
363 .unwrap()
364 .is_null(0)
365 {
366 self.len() - 1
368 } else {
369 self.len() - self.null_count() - 1
371 };
372
373 debug_assert!(
374 unsafe { self.get_unchecked(out) }.is_some(),
376 "incorrect sorted flag"
377 );
378
379 Some(out)
380 } else {
381 last_non_null(self.chunks().iter().map(|arr| arr.as_ref()), self.len())
382 }
383 }
384
385 pub fn drop_nulls(&self) -> Self {
386 if self.null_count() == 0 {
387 self.clone()
388 } else {
389 let chunks = self
390 .downcast_iter()
391 .map(|arr| {
392 if arr.null_count() == 0 {
393 arr.to_boxed()
394 } else {
395 filter_with_bitmap(arr, arr.validity().unwrap())
396 }
397 })
398 .collect();
399 unsafe {
400 Self::new_with_dims(
401 self.field.clone(),
402 chunks,
403 self.len() - self.null_count(),
404 0,
405 )
406 }
407 }
408 }
409
410 #[inline]
412 #[allow(clippy::type_complexity)]
413 pub fn iter_validities(
414 &self,
415 ) -> impl ExactSizeIterator<Item = Option<&Bitmap>> + DoubleEndedIterator {
416 fn to_validity(arr: &ArrayRef) -> Option<&Bitmap> {
417 arr.validity()
418 }
419 self.chunks.iter().map(to_validity)
420 }
421
422 #[inline]
423 pub fn has_nulls(&self) -> bool {
425 self.null_count > 0
426 }
427
428 pub fn shrink_to_fit(&mut self) {
430 self.chunks = vec![concatenate_unchecked(self.chunks.as_slice()).unwrap()];
431 }
432
433 pub fn clear(&self) -> Self {
434 let mut ca = unsafe {
436 self.copy_with_chunks(vec![new_empty_array(
437 self.chunks.first().unwrap().dtype().clone(),
438 )])
439 };
440
441 use StatisticsFlags as F;
442 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
443 ca
444 }
445
446 pub(crate) unsafe fn unpack_series_matching_physical_type<'a>(
453 &self,
454 series: &'a Series,
455 ) -> &'a ChunkedArray<T> {
456 let series_trait = &**series;
457 if self.dtype() == series.dtype() {
458 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
459 } else {
460 use DataType::*;
461 match (self.dtype(), series.dtype()) {
462 (Int64, Datetime(_, _)) | (Int64, Duration(_)) | (Int32, Date) => {
463 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
464 },
465 _ => panic!(
466 "cannot unpack series {:?} into matching type {:?}",
467 series,
468 self.dtype()
469 ),
470 }
471 }
472 }
473
474 pub fn chunk_lengths(&self) -> ChunkLenIter<'_> {
476 self.chunks.iter().map(|chunk| chunk.len())
477 }
478
479 #[inline]
481 pub fn chunks(&self) -> &Vec<ArrayRef> {
482 &self.chunks
483 }
484
485 #[inline]
491 pub unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef> {
492 &mut self.chunks
493 }
494
495 pub fn is_optimal_aligned(&self) -> bool {
497 self.chunks.len() == 1 && self.null_count() == 0
498 }
499
500 unsafe fn copy_with_chunks(&self, chunks: Vec<ArrayRef>) -> Self {
505 Self::new_with_compute_len(self.field.clone(), chunks)
506 }
507
508 pub fn dtype(&self) -> &DataType {
510 self.field.dtype()
511 }
512
513 pub(crate) unsafe fn set_dtype(&mut self, dtype: DataType) {
514 self.field = Arc::new(Field::new(self.name().clone(), dtype))
515 }
516
517 pub fn name(&self) -> &PlSmallStr {
519 self.field.name()
520 }
521
522 pub fn ref_field(&self) -> &Field {
524 &self.field
525 }
526
527 pub fn rename(&mut self, name: PlSmallStr) {
529 self.field = Arc::new(Field::new(name, self.field.dtype().clone()));
530 }
531
532 pub fn with_name(mut self, name: PlSmallStr) -> Self {
534 self.rename(name);
535 self
536 }
537}
538
539impl<T> ChunkedArray<T>
540where
541 T: PolarsDataType,
542{
543 #[inline]
549 pub fn get(&self, idx: usize) -> Option<T::Physical<'_>> {
550 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
551 assert!(
552 chunk_idx < self.chunks().len(),
553 "index: {} out of bounds for len: {}",
554 idx,
555 self.len()
556 );
557 unsafe {
558 let arr = self.downcast_get_unchecked(chunk_idx);
559 assert!(
560 arr_idx < arr.len(),
561 "index: {} out of bounds for len: {}",
562 idx,
563 self.len()
564 );
565 arr.get_unchecked(arr_idx)
566 }
567 }
568
569 #[inline]
575 pub unsafe fn get_unchecked(&self, idx: usize) -> Option<T::Physical<'_>> {
576 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
577
578 unsafe {
579 self.downcast_get_unchecked(chunk_idx)
581 .get_unchecked(arr_idx)
582 }
583 }
584
585 #[inline]
591 pub unsafe fn value_unchecked(&self, idx: usize) -> T::Physical<'_> {
592 let (chunk_idx, arr_idx) = self.index_to_chunked_index(idx);
593
594 unsafe {
595 self.downcast_get_unchecked(chunk_idx)
597 .value_unchecked(arr_idx)
598 }
599 }
600
601 #[inline]
604 pub fn first(&self) -> Option<T::Physical<'_>> {
605 self.iter().next().unwrap()
606 }
607
608 #[inline]
611 pub fn last(&self) -> Option<T::Physical<'_>> {
612 let arr = self
613 .downcast_iter()
614 .rev()
615 .find(|arr| !arr.is_empty())
616 .unwrap();
617 unsafe { arr.get_unchecked(arr.len() - 1) }
618 }
619
620 pub fn set_validity(&mut self, validity: Option<Bitmap>) {
621 assert!(
622 !self.dtype().is_struct(),
623 "set_outer_validity should be used for struct types"
624 );
625 if let Some(v) = &validity {
626 assert_eq!(self.len(), v.len());
627 }
628 let mut i = 0;
629 for chunk in unsafe { self.chunks_mut() } {
630 *chunk =
631 chunk.with_validity(validity.as_ref().map(|v| v.clone().sliced(i, chunk.len())));
632 i += chunk.len();
633 }
634 self.null_count = validity.map(|v| v.unset_bits()).unwrap_or(0);
635 self.set_fast_explode_list(false);
636 }
637
638 pub fn with_validity(mut self, validity: Option<Bitmap>) -> Self {
639 self.set_validity(validity);
640 self
641 }
642}
643
644impl<T> ChunkedArray<T>
645where
646 T: PolarsDataType,
647 ChunkedArray<T>: ChunkExpandAtIndex<T>,
648{
649 pub fn broadcast_to(&self, length: usize) -> PolarsResult<Cow<'_, Self>> {
653 let len = self.len();
654 if len == length {
655 Ok(Cow::Borrowed(self))
656 } else if len == 1 {
657 Ok(Cow::Owned(self.new_from_index(0, length)))
658 } else {
659 polars_bail!(
660 ShapeMismatch: "can't broadcast Series '{}' of length {len} to length {length}",
661 self.name()
662 );
663 }
664 }
665
666 pub fn broadcast_in_place_to(&mut self, length: usize) -> PolarsResult<()> {
668 if let Cow::Owned(new) = self.broadcast_to(length)? {
669 *self = new;
670 }
671 Ok(())
672 }
673
674 pub fn broadcast_owned_to(mut self, length: usize) -> PolarsResult<Self> {
676 self.broadcast_in_place_to(length)?;
677 Ok(self)
678 }
679}
680
681impl<T> ChunkedArray<T>
682where
683 T: PolarsDataType,
684 ChunkedArray<T>: ChunkTakeUnchecked<[IdxSize]>,
685{
686 pub fn deposit(&self, validity: &Bitmap) -> Self {
688 let set_bits = validity.set_bits();
689
690 assert_eq!(self.len(), set_bits);
691
692 if set_bits == validity.len() {
693 return self.clone();
694 }
695
696 if set_bits == 0 {
697 return Self::full_null_like(self, validity.len());
698 }
699
700 let mut null_mask = validity.clone();
701
702 let mut gather_idxs = Vec::with_capacity(validity.len());
703 let leading_nulls = null_mask.take_leading_zeros();
704 gather_idxs.extend(std::iter::repeat_n(0, leading_nulls + 1));
705
706 let mut i = 0 as IdxSize;
707 gather_idxs.extend(null_mask.iter().skip(1).map(|v| {
708 i += IdxSize::from(v);
709 i
710 }));
711
712 let mut ca = unsafe { ChunkTakeUnchecked::take_unchecked(self, &gather_idxs) };
713 ca.set_validity(combine_validities_and(
714 Some(validity),
715 ca.rechunk_validity().as_ref(),
716 ));
717 ca
718 }
719}
720
721impl ListChunked {
722 #[inline]
723 pub fn get_as_series(&self, idx: usize) -> Option<Series> {
724 unsafe {
725 Some(Series::from_chunks_and_dtype_unchecked(
726 self.name().clone(),
727 vec![self.get(idx)?],
728 &self.inner_dtype().to_physical(),
729 ))
730 }
731 }
732
733 pub fn has_empty_lists(&self) -> bool {
734 for arr in self.downcast_iter() {
735 if arr.is_empty() {
736 continue;
737 }
738
739 if match arr.validity() {
740 None => arr.offsets().lengths().any(|l| l == 0),
741 Some(validity) => arr
742 .offsets()
743 .lengths()
744 .enumerate()
745 .any(|(i, l)| l == 0 && unsafe { validity.get_bit_unchecked(i) }),
746 } {
747 return true;
748 }
749 }
750
751 false
752 }
753
754 pub fn has_masked_out_values(&self) -> bool {
755 for arr in self.downcast_iter() {
756 if arr.is_empty() {
757 continue;
758 }
759
760 if *arr.offsets().first() != 0 || *arr.offsets().last() != arr.values().len() as i64 {
761 return true;
762 }
763
764 let Some(validity) = arr.validity() else {
765 continue;
766 };
767 if validity.set_bits() == 0 {
768 continue;
769 }
770
771 for i in (!validity).true_idx_iter() {
773 if arr.offsets().length_at(i) > 0 {
774 return true;
775 }
776 }
777 }
778
779 false
780 }
781}
782
783#[cfg(feature = "dtype-array")]
784impl ArrayChunked {
785 #[inline]
786 pub fn get_as_series(&self, idx: usize) -> Option<Series> {
787 unsafe {
788 Some(Series::from_chunks_and_dtype_unchecked(
789 self.name().clone(),
790 vec![self.get(idx)?],
791 &self.inner_dtype().to_physical(),
792 ))
793 }
794 }
795
796 pub fn from_aligned_values(
797 name: PlSmallStr,
798 inner_dtype: &DataType,
799 width: usize,
800 chunks: Vec<ArrayRef>,
801 length: usize,
802 ) -> Self {
803 let dtype = DataType::Array(Box::new(inner_dtype.clone()), width);
804 let arrow_dtype = inner_dtype
805 .to_physical()
806 .to_arrow(CompatLevel::newest())
807 .to_fixed_size_list(width, true);
808 let field = Arc::new(Field::new(name, dtype));
809 if width == 0 {
810 use arrow::array::builder::{ArrayBuilder, make_builder};
811 let values = make_builder(&inner_dtype.to_arrow(CompatLevel::newest())).freeze();
812 return ArrayChunked::new_with_compute_len(
813 field,
814 vec![FixedSizeListArray::new(arrow_dtype, length, values, None).into_boxed()],
815 );
816 }
817 let mut total_len = 0;
818 let chunks = chunks
819 .iter()
820 .map(|chunk| {
821 debug_assert_eq!(chunk.len() % width, 0);
822 let chunk_len = chunk.len() / width;
823 total_len += chunk_len;
824 FixedSizeListArray::new(arrow_dtype.clone(), chunk_len, chunk.clone(), None)
825 .into_boxed()
826 })
827 .collect();
828 debug_assert_eq!(total_len, length);
829
830 unsafe { Self::new_with_dims(field, chunks, length, 0) }
831 }
832
833 pub fn to_list(&self) -> ListChunked {
837 let inner_dtype = self.inner_dtype();
838 let chunks = self
839 .downcast_iter()
840 .map(|chunk| {
841 use arrow::offset::OffsetsBuffer;
842
843 let inner_dtype = chunk.dtype().inner_dtype().unwrap();
844 let dtype = inner_dtype.clone().to_large_list(true);
845
846 let offsets = (0..=chunk.len())
847 .map(|i| (i * self.width()) as i64)
848 .collect::<Vec<i64>>();
849
850 let offsets = unsafe { OffsetsBuffer::new_unchecked(offsets.into()) };
852
853 ListArray::<i64>::new(
854 dtype,
855 offsets,
856 chunk.values().clone(),
857 chunk.validity().cloned(),
858 )
859 .into_boxed()
860 })
861 .collect();
862
863 let mut ca = unsafe {
865 ListChunked::new_with_dims(
866 Arc::new(Field::new(
867 self.name().clone(),
868 DataType::List(Box::new(inner_dtype.clone())),
869 )),
870 chunks,
871 self.len(),
872 self.null_count(),
873 )
874 };
875 ca.set_fast_explode_list(!self.has_nulls());
876 ca
877 }
878}
879
880impl<T> ChunkedArray<T>
881where
882 T: PolarsDataType,
883{
884 pub fn match_chunks<I>(&self, chunk_id: I) -> Self
888 where
889 I: Iterator<Item = usize>,
890 {
891 debug_assert!(self.chunks.len() == 1);
892 let slice = |ca: &Self| {
894 let array = &ca.chunks[0];
895
896 let mut offset = 0;
897 let chunks = chunk_id
898 .map(|len| {
899 debug_assert!((offset + len) <= array.len());
901 let out = unsafe { array.sliced_unchecked(offset, len) };
902 offset += len;
903 out
904 })
905 .collect();
906
907 debug_assert_eq!(offset, array.len());
908
909 unsafe {
911 Self::from_chunks_and_dtype(self.name().clone(), chunks, self.dtype().clone())
912 }
913 };
914
915 if self.chunks.len() != 1 {
916 let out = self.rechunk();
917 slice(&out)
918 } else {
919 slice(self)
920 }
921 }
922}
923
924impl<T: PolarsDataType> AsRefDataType for ChunkedArray<T> {
925 fn as_ref_dtype(&self) -> &DataType {
926 self.dtype()
927 }
928}
929
930pub(crate) trait AsSinglePtr: AsRefDataType {
931 fn as_single_ptr(&mut self) -> PolarsResult<usize> {
933 polars_bail!(opq = as_single_ptr, self.as_ref_dtype());
934 }
935}
936
937impl<T> AsSinglePtr for ChunkedArray<T>
938where
939 T: PolarsNumericType,
940{
941 fn as_single_ptr(&mut self) -> PolarsResult<usize> {
942 self.rechunk_mut();
943 let a = self.data_views().next().unwrap();
944 let ptr = a.as_ptr();
945 Ok(ptr as usize)
946 }
947}
948
949impl AsSinglePtr for BooleanChunked {}
950impl AsSinglePtr for ListChunked {}
951#[cfg(feature = "dtype-array")]
952impl AsSinglePtr for ArrayChunked {}
953impl AsSinglePtr for StringChunked {}
954impl AsSinglePtr for BinaryChunked {}
955#[cfg(feature = "object")]
956impl<T: PolarsObject> AsSinglePtr for ObjectChunked<T> {}
957
958pub enum ChunkedArrayLayout<'a, T: PolarsDataType> {
959 SingleNoNull(&'a T::Array),
960 Single(&'a T::Array),
961 MultiNoNull(&'a ChunkedArray<T>),
962 Multi(&'a ChunkedArray<T>),
963}
964
965impl<T> ChunkedArray<T>
966where
967 T: PolarsDataType,
968{
969 pub fn layout(&self) -> ChunkedArrayLayout<'_, T> {
970 if self.chunks.len() == 1 {
971 let arr = self.downcast_iter().next().unwrap();
972 return if arr.null_count() == 0 {
973 ChunkedArrayLayout::SingleNoNull(arr)
974 } else {
975 ChunkedArrayLayout::Single(arr)
976 };
977 }
978
979 if self.downcast_iter().all(|a| a.null_count() == 0) {
980 ChunkedArrayLayout::MultiNoNull(self)
981 } else {
982 ChunkedArrayLayout::Multi(self)
983 }
984 }
985}
986
987impl<T> ChunkedArray<T>
988where
989 T: PolarsNumericType,
990{
991 pub fn cont_slice(&self) -> PolarsResult<&[T::Native]> {
993 polars_ensure!(
994 self.chunks.len() == 1 && self.chunks[0].null_count() == 0,
995 ComputeError: "chunked array is not contiguous"
996 );
997 Ok(self.downcast_iter().next().map(|arr| arr.values()).unwrap())
998 }
999
1000 pub(crate) fn cont_slice_mut(&mut self) -> Option<&mut [T::Native]> {
1002 if self.chunks.len() == 1 && self.chunks[0].null_count() == 0 {
1003 let arr = unsafe { self.downcast_iter_mut().next().unwrap() };
1005 arr.get_mut_values()
1006 } else {
1007 None
1008 }
1009 }
1010
1011 pub fn data_views(&self) -> impl DoubleEndedIterator<Item = &[T::Native]> {
1015 self.downcast_iter().map(|arr| arr.values().as_slice())
1016 }
1017
1018 #[allow(clippy::wrong_self_convention)]
1019 pub fn into_no_null_iter(
1020 &self,
1021 ) -> impl '_ + Send + Sync + ExactSizeIterator<Item = T::Native> + DoubleEndedIterator + TrustedLen
1022 {
1023 #[allow(clippy::map_clone)]
1025 unsafe {
1027 self.data_views()
1028 .flatten()
1029 .map(|v| *v)
1030 .trust_my_length(self.len())
1031 }
1032 }
1033}
1034
1035impl<T: PolarsDataType> Clone for ChunkedArray<T> {
1036 fn clone(&self) -> Self {
1037 ChunkedArray {
1038 field: self.field.clone(),
1039 chunks: self.chunks.clone(),
1040 flags: self.flags.clone(),
1041
1042 _pd: Default::default(),
1043 length: self.length,
1044 null_count: self.null_count,
1045 }
1046 }
1047}
1048
1049impl<T: PolarsDataType> AsRef<ChunkedArray<T>> for ChunkedArray<T> {
1050 fn as_ref(&self) -> &ChunkedArray<T> {
1051 self
1052 }
1053}
1054
1055impl ValueSize for ListChunked {
1056 fn get_values_size(&self) -> usize {
1057 self.chunks
1058 .iter()
1059 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1060 }
1061}
1062
1063#[cfg(feature = "dtype-array")]
1064impl ValueSize for ArrayChunked {
1065 fn get_values_size(&self) -> usize {
1066 self.chunks
1067 .iter()
1068 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1069 }
1070}
1071impl ValueSize for StringChunked {
1072 fn get_values_size(&self) -> usize {
1073 self.chunks
1074 .iter()
1075 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1076 }
1077}
1078
1079impl ValueSize for BinaryOffsetChunked {
1080 fn get_values_size(&self) -> usize {
1081 self.chunks
1082 .iter()
1083 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1084 }
1085}
1086
1087pub(crate) fn to_primitive<T: PolarsNumericType>(
1088 values: Vec<T::Native>,
1089 validity: Option<Bitmap>,
1090) -> PrimitiveArray<T::Native> {
1091 PrimitiveArray::new(
1092 T::get_static_dtype().to_arrow(CompatLevel::newest()),
1093 values.into(),
1094 validity,
1095 )
1096}
1097
1098pub(crate) fn to_array<T: PolarsNumericType>(
1099 values: Vec<T::Native>,
1100 validity: Option<Bitmap>,
1101) -> ArrayRef {
1102 Box::new(to_primitive::<T>(values, validity))
1103}
1104
1105impl<T: PolarsDataType> Default for ChunkedArray<T> {
1106 fn default() -> Self {
1107 let dtype = T::get_static_dtype();
1108 let arrow_dtype = dtype.to_physical().to_arrow(CompatLevel::newest());
1109 ChunkedArray {
1110 field: Arc::new(Field::new(PlSmallStr::EMPTY, dtype)),
1111 chunks: vec![new_empty_array(arrow_dtype)],
1113 flags: StatisticsFlagsIM::empty(),
1114
1115 _pd: Default::default(),
1116 length: 0,
1117 null_count: 0,
1118 }
1119 }
1120}
1121
1122impl<T: PolarsDataType> BroadcastLength for ChunkedArray<T> {
1123 fn _broadcast_len(&self) -> usize {
1124 self.len()
1125 }
1126
1127 fn _column_name(&self) -> Option<&str> {
1128 Some(self.name())
1129 }
1130}
1131
1132#[cfg(test)]
1133pub(crate) mod test {
1134 use crate::prelude::*;
1135
1136 pub(crate) fn get_chunked_array() -> Int32Chunked {
1137 ChunkedArray::new(PlSmallStr::from_static("a"), &[1, 2, 3])
1138 }
1139
1140 #[test]
1141 fn test_sort() {
1142 let a = Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 9, 3, 2]);
1143 let b = a
1144 .sort(false)
1145 .iter()
1146 .map(|opt| opt.unwrap())
1147 .collect::<Vec<_>>();
1148 assert_eq!(b, [1, 2, 3, 9]);
1149 let a = StringChunked::new(PlSmallStr::from_static("a"), &["b", "a", "c"]);
1150 let a = a.sort(false);
1151 let b = a.iter().collect::<Vec<_>>();
1152 assert_eq!(b, [Some("a"), Some("b"), Some("c")]);
1153 assert!(a.is_sorted_ascending_flag());
1154 }
1155
1156 #[test]
1157 fn arithmetic() {
1158 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 6, 40]);
1159 let b = &Int32Chunked::new(PlSmallStr::from_static("b"), &[-1, 2, 3, 4]);
1160
1161 println!("{:?}", a + b);
1164 println!("{:?}", a - b);
1165 println!("{:?}", a * b);
1166 println!("{:?}", a / b);
1167 }
1168
1169 #[test]
1170 fn iter() {
1171 let s1 = get_chunked_array();
1172 assert_eq!(s1.iter().fold(0, |acc, val| { acc + val.unwrap() }), 6)
1174 }
1175
1176 #[test]
1177 fn limit() {
1178 let a = get_chunked_array();
1179 let b = a.limit(2);
1180 println!("{b:?}");
1181 assert_eq!(b.len(), 2)
1182 }
1183
1184 #[test]
1185 fn filter() {
1186 let a = get_chunked_array();
1187 let b = a
1188 .filter(&BooleanChunked::new(
1189 PlSmallStr::from_static("filter"),
1190 &[true, false, false],
1191 ))
1192 .unwrap();
1193 assert_eq!(b.len(), 1);
1194 assert_eq!(b.iter().next(), Some(Some(1)));
1195 }
1196
1197 #[test]
1198 fn aggregates() {
1199 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 10, 9]);
1200 assert_eq!(a.max(), Some(100));
1201 assert_eq!(a.min(), Some(1));
1202 assert_eq!(a.sum(), Some(120))
1203 }
1204
1205 #[test]
1206 fn take() {
1207 let a = get_chunked_array();
1208 let new = a.take(&[0 as IdxSize, 1]).unwrap();
1209 assert_eq!(new.len(), 2)
1210 }
1211
1212 #[test]
1213 fn cast() {
1214 let a = get_chunked_array();
1215 let b = a.cast(&DataType::Int64).unwrap();
1216 assert_eq!(b.dtype(), &DataType::Int64)
1217 }
1218
1219 fn assert_slice_equal<T>(ca: &ChunkedArray<T>, eq: &[T::Native])
1220 where
1221 T: PolarsNumericType,
1222 {
1223 assert_eq!(ca.iter().map(|opt| opt.unwrap()).collect::<Vec<_>>(), eq)
1224 }
1225
1226 #[test]
1227 fn slice() {
1228 let mut first = UInt32Chunked::new(PlSmallStr::from_static("first"), &[0, 1, 2]);
1229 let second = UInt32Chunked::new(PlSmallStr::from_static("second"), &[3, 4, 5]);
1230 first.append(&second).unwrap();
1231 assert_slice_equal(&first.slice(0, 3), &[0, 1, 2]);
1232 assert_slice_equal(&first.slice(0, 4), &[0, 1, 2, 3]);
1233 assert_slice_equal(&first.slice(1, 4), &[1, 2, 3, 4]);
1234 assert_slice_equal(&first.slice(3, 2), &[3, 4]);
1235 assert_slice_equal(&first.slice(3, 3), &[3, 4, 5]);
1236 assert_slice_equal(&first.slice(-3, 3), &[3, 4, 5]);
1237 assert_slice_equal(&first.slice(-6, 6), &[0, 1, 2, 3, 4, 5]);
1238
1239 assert_eq!(first.slice(-7, 2).len(), 1);
1240 assert_eq!(first.slice(-3, 4).len(), 3);
1241 assert_eq!(first.slice(3, 4).len(), 3);
1242 assert_eq!(first.slice(10, 4).len(), 0);
1243 }
1244
1245 #[test]
1246 fn sorting() {
1247 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[9, 2, 4]);
1248 let sorted = s.sort(false);
1249 assert_slice_equal(&sorted, &[2, 4, 9]);
1250 let sorted = s.sort(true);
1251 assert_slice_equal(&sorted, &[9, 4, 2]);
1252
1253 let s: StringChunked = ["b", "a", "z"].iter().collect();
1254 let sorted = s.sort(false);
1255 assert_eq!(
1256 sorted.iter().collect::<Vec<_>>(),
1257 &[Some("a"), Some("b"), Some("z")]
1258 );
1259 let sorted = s.sort(true);
1260 assert_eq!(
1261 sorted.iter().collect::<Vec<_>>(),
1262 &[Some("z"), Some("b"), Some("a")]
1263 );
1264 let s: StringChunked = [Some("b"), None, Some("z")].iter().copied().collect();
1265 let sorted = s.sort(false);
1266 assert_eq!(
1267 sorted.iter().collect::<Vec<_>>(),
1268 &[None, Some("b"), Some("z")]
1269 );
1270 }
1271
1272 #[test]
1273 fn reverse() {
1274 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[1, 2, 3]);
1275 assert_slice_equal(&s.reverse(), &[3, 2, 1]);
1277 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[Some(1), None, Some(3)]);
1279 assert_eq!(Vec::from(&s.reverse()), &[Some(3), None, Some(1)]);
1280 let s = BooleanChunked::new(PlSmallStr::EMPTY, &[true, false]);
1281 assert_eq!(Vec::from(&s.reverse()), &[Some(false), Some(true)]);
1282
1283 let s = StringChunked::new(PlSmallStr::EMPTY, &["a", "b", "c"]);
1284 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), Some("b"), Some("a")]);
1285
1286 let s = StringChunked::new(PlSmallStr::EMPTY, &[Some("a"), None, Some("c")]);
1287 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), None, Some("a")]);
1288 }
1289
1290 #[test]
1291 #[cfg(feature = "dtype-categorical")]
1292 fn test_iter_categorical() {
1293 let ca = StringChunked::new(
1294 PlSmallStr::EMPTY,
1295 &[Some("foo"), None, Some("bar"), Some("ham")],
1296 );
1297 let cats = Categories::new(
1298 PlSmallStr::EMPTY,
1299 PlSmallStr::EMPTY,
1300 CategoricalPhysical::U32,
1301 );
1302 let ca = ca.cast(&DataType::from_categories(cats)).unwrap();
1303 let ca = ca.cat32().unwrap();
1304 let v: Vec<_> = ca.physical().iter().collect();
1305 assert_eq!(v, &[Some(0), None, Some(1), Some(2)]);
1306 }
1307
1308 #[test]
1309 #[ignore]
1310 fn test_shrink_to_fit() {
1311 let mut builder = StringChunkedBuilder::new(PlSmallStr::from_static("foo"), 2048);
1312 builder.append_value("foo");
1313 let mut arr = builder.finish();
1314 let before = arr
1315 .chunks()
1316 .iter()
1317 .map(|arr| arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1318 .sum::<usize>();
1319 arr.shrink_to_fit();
1320 let after = arr
1321 .chunks()
1322 .iter()
1323 .map(|arr| arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1324 .sum::<usize>();
1325 assert!(before > after);
1326 }
1327}