1#![allow(unsafe_op_in_unsafe_fn)]
3use std::borrow::Cow;
4use std::sync::Arc;
5
6use num_traits::Signed;
7use polars_arrow::array::*;
8use polars_arrow::bitmap::Bitmap;
9use polars_arrow::compute::concatenate::concatenate_unchecked;
10use polars_arrow::compute::utils::combine_validities_and;
11use polars_compute::filter::filter_with_bitmap;
12use polars_utils::broadcast::BroadcastLength;
13
14use crate::prelude::{ChunkTakeUnchecked, *};
15
16pub mod ops;
17#[macro_use]
18pub mod arithmetic;
19pub mod builder;
20pub mod cast;
21pub mod collect;
22pub mod comparison;
23pub mod flags;
24pub mod float;
25pub mod iterator;
26#[cfg(feature = "ndarray")]
27pub(crate) mod ndarray;
28
29pub mod arg_min_max;
30#[cfg(feature = "dtype-array")]
31pub(crate) mod array;
32mod binary;
33mod binary_offset;
34mod bitwise;
35#[cfg(feature = "object")]
36mod drop;
37mod from;
38mod from_iterator;
39pub mod from_iterator_par;
40pub(crate) mod list;
41pub(crate) mod logical;
42#[cfg(feature = "object")]
43pub mod object;
44#[cfg(feature = "random")]
45mod random;
46#[cfg(feature = "dtype-struct")]
47mod struct_;
48#[cfg(any(
49 feature = "temporal",
50 feature = "dtype-datetime",
51 feature = "dtype-date"
52))]
53pub mod temporal;
54mod to_vec;
55mod trusted_len;
56pub(crate) use arg_min_max::*;
57#[cfg(feature = "dtype-struct")]
58pub use struct_::StructChunked;
59
60use self::flags::{StatisticsFlags, StatisticsFlagsIM};
61use crate::series::IsSorted;
62use crate::utils::{first_non_null, first_null, last_non_null};
63
64pub type ChunkLenIter<'a> = std::iter::Map<std::slice::Iter<'a, ArrayRef>, fn(&ArrayRef) -> usize>;
65
66pub struct ChunkedArray<T: PolarsDataType> {
141 pub(crate) field: Arc<Field>,
142 pub(crate) chunks: Vec<ArrayRef>,
143
144 pub(crate) flags: StatisticsFlagsIM,
145
146 length: usize,
147 null_count: usize,
148 _pd: std::marker::PhantomData<T>,
149}
150
151impl<T: PolarsDataType> ChunkedArray<T> {
152 fn should_rechunk(&self) -> bool {
153 self.chunks.len() > 1 && self.chunks.len() > self.len() / 3
154 }
155
156 fn optional_rechunk(mut self) -> Self {
157 if self.should_rechunk() {
159 self.rechunk_mut()
160 }
161 self
162 }
163
164 pub(crate) fn as_any(&self) -> &dyn std::any::Any {
165 self
166 }
167
168 pub fn unpack_series_matching_type<'a>(
170 &self,
171 series: &'a Series,
172 ) -> PolarsResult<&'a ChunkedArray<T>> {
173 polars_ensure!(
174 self.dtype() == series.dtype(),
175 SchemaMismatch: "cannot unpack series of type `{}` into `{}`",
176 series.dtype(),
177 self.dtype(),
178 );
179
180 Ok(unsafe { self.unpack_series_matching_physical_type(series) })
182 }
183
184 fn new_with_compute_len(field: Arc<Field>, chunks: Vec<ArrayRef>) -> Self {
189 unsafe {
190 let mut chunked_arr = Self::new_with_dims(field, chunks, 0, 0);
191 chunked_arr.compute_len();
192 chunked_arr
193 }
194 }
195
196 pub unsafe fn new_with_dims(
200 field: Arc<Field>,
201 chunks: Vec<ArrayRef>,
202 length: usize,
203 null_count: usize,
204 ) -> Self {
205 Self {
206 field,
207 chunks,
208 flags: StatisticsFlagsIM::empty(),
209
210 _pd: Default::default(),
211 length,
212 null_count,
213 }
214 }
215
216 pub(crate) fn is_sorted_ascending_flag(&self) -> bool {
217 self.get_flags().is_sorted_ascending()
218 }
219
220 pub(crate) fn is_sorted_descending_flag(&self) -> bool {
221 self.get_flags().is_sorted_descending()
222 }
223
224 pub(crate) fn is_sorted_any(&self) -> bool {
226 self.get_flags().is_sorted_any()
227 }
228
229 pub fn unset_fast_explode_list(&mut self) {
230 self.set_fast_explode_list(false)
231 }
232
233 pub fn set_fast_explode_list(&mut self, value: bool) {
234 let mut flags = self.flags.get_mut();
235 flags.set(StatisticsFlags::CAN_FAST_EXPLODE_LIST, value);
236 self.flags.set_mut(flags);
237 }
238
239 pub fn get_fast_explode_list(&self) -> bool {
240 self.get_flags().can_fast_explode_list()
241 }
242
243 #[inline]
244 pub fn get_flags(&self) -> StatisticsFlags {
245 self.flags.get()
246 }
247
248 pub fn set_flags(&mut self, flags: StatisticsFlags) {
250 self.flags = StatisticsFlagsIM::new(flags);
251 }
252
253 pub fn is_sorted_flag(&self) -> IsSorted {
254 self.get_flags().is_sorted()
255 }
256
257 pub fn retain_flags_from<U: PolarsDataType>(
258 &mut self,
259 from: &ChunkedArray<U>,
260 retain_flags: StatisticsFlags,
261 ) {
262 let flags = from.flags.get();
263 if !flags.is_empty() {
265 self.set_flags(flags & retain_flags)
266 }
267 }
268
269 pub fn set_sorted_flag(&mut self, sorted: IsSorted) {
271 let mut flags = self.flags.get_mut();
272 flags.set_sorted(sorted);
273 self.flags.set_mut(flags);
274 }
275
276 pub fn with_sorted_flag(&self, sorted: IsSorted) -> Self {
278 let mut out = self.clone();
279 out.set_sorted_flag(sorted);
280 out
281 }
282
283 pub fn first_null(&self) -> Option<usize> {
284 if self.null_count() == 0 {
285 None
286 }
287 else if self.null_count() == self.len() {
289 Some(0)
290 } else if self.is_sorted_any() {
291 let out = if self
292 .chunks
293 .iter()
294 .find(|arr| !arr.is_empty())
295 .unwrap()
296 .is_null(0)
297 {
298 0
300 } else {
301 self.len() - self.null_count()
303 };
304
305 debug_assert!(
306 unsafe { self.get_unchecked(out) }.is_none(),
308 "incorrect sorted flag"
309 );
310
311 Some(out)
312 } else {
313 first_null(self.chunks().iter().map(|arr| arr.as_ref()))
314 }
315 }
316
317 pub fn first_non_null(&self) -> Option<usize> {
319 if self.null_count() == self.len() {
320 None
321 }
322 else if self.null_count() == 0 {
324 Some(0)
325 } else if self.is_sorted_any() {
326 let out = if self
327 .chunks
328 .iter()
329 .find(|arr| !arr.is_empty())
330 .unwrap()
331 .is_null(0)
332 {
333 self.null_count()
335 } else {
336 0
338 };
339
340 debug_assert!(
341 unsafe { self.get_unchecked(out) }.is_some(),
343 "incorrect sorted flag"
344 );
345
346 Some(out)
347 } else {
348 first_non_null(self.chunks().iter().map(|arr| arr.as_ref()))
349 }
350 }
351
352 pub fn last_non_null(&self) -> Option<usize> {
354 if self.null_count() == self.len() {
355 None
356 }
357 else if self.null_count() == 0 {
359 Some(self.len() - 1)
360 } else if self.is_sorted_any() {
361 let out = if self
362 .chunks
363 .iter()
364 .find(|arr| !arr.is_empty())
365 .unwrap()
366 .is_null(0)
367 {
368 self.len() - 1
370 } else {
371 self.len() - self.null_count() - 1
373 };
374
375 debug_assert!(
376 unsafe { self.get_unchecked(out) }.is_some(),
378 "incorrect sorted flag"
379 );
380
381 Some(out)
382 } else {
383 last_non_null(self.chunks().iter().map(|arr| arr.as_ref()), self.len())
384 }
385 }
386
387 pub fn drop_nulls(&self) -> Self {
388 if self.null_count() == 0 {
389 self.clone()
390 } else {
391 let chunks = self
392 .downcast_iter()
393 .map(|arr| {
394 if arr.null_count() == 0 {
395 arr.to_boxed()
396 } else {
397 filter_with_bitmap(arr, arr.validity().unwrap())
398 }
399 })
400 .collect();
401 unsafe {
402 Self::new_with_dims(
403 self.field.clone(),
404 chunks,
405 self.len() - self.null_count(),
406 0,
407 )
408 }
409 }
410 }
411
412 #[inline]
414 #[allow(clippy::type_complexity)]
415 pub fn iter_validities(
416 &self,
417 ) -> impl ExactSizeIterator<Item = Option<&Bitmap>> + DoubleEndedIterator {
418 fn to_validity(arr: &ArrayRef) -> Option<&Bitmap> {
419 arr.validity()
420 }
421 self.chunks.iter().map(to_validity)
422 }
423
424 #[inline]
425 pub fn has_nulls(&self) -> bool {
427 self.null_count > 0
428 }
429
430 pub fn shrink_to_fit(&mut self) {
432 self.chunks = vec![concatenate_unchecked(self.chunks.as_slice()).unwrap()];
433 }
434
435 pub fn clear(&self) -> Self {
436 let mut ca = unsafe {
438 self.copy_with_chunks(vec![new_empty_array(
439 self.chunks.first().unwrap().dtype().clone(),
440 )])
441 };
442
443 use StatisticsFlags as F;
444 ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
445 ca
446 }
447
448 pub(crate) unsafe fn unpack_series_matching_physical_type<'a>(
455 &self,
456 series: &'a Series,
457 ) -> &'a ChunkedArray<T> {
458 let series_trait = &**series;
459 if self.dtype() == series.dtype() {
460 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
461 } else {
462 use DataType::*;
463 match (self.dtype(), series.dtype()) {
464 (Int64, Datetime(_, _)) | (Int64, Duration(_)) | (Int32, Date) => {
465 &*(series_trait as *const dyn SeriesTrait as *const ChunkedArray<T>)
466 },
467 _ => panic!(
468 "cannot unpack series {:?} into matching type {:?}",
469 series,
470 self.dtype()
471 ),
472 }
473 }
474 }
475
476 pub fn chunk_lengths(&self) -> ChunkLenIter<'_> {
478 self.chunks.iter().map(|chunk| chunk.len())
479 }
480
481 #[inline]
483 pub fn chunks(&self) -> &Vec<ArrayRef> {
484 &self.chunks
485 }
486
487 #[inline]
493 pub unsafe fn chunks_mut(&mut self) -> &mut Vec<ArrayRef> {
494 &mut self.chunks
495 }
496
497 unsafe fn copy_with_chunks(&self, chunks: Vec<ArrayRef>) -> Self {
502 Self::new_with_compute_len(self.field.clone(), chunks)
503 }
504
505 #[inline(always)]
507 pub fn dtype(&self) -> &DataType {
508 self.field.dtype()
509 }
510
511 pub(crate) unsafe fn set_dtype(&mut self, dtype: DataType) {
512 self.field = Arc::new(Field::new(self.name().clone(), dtype))
513 }
514
515 #[inline]
517 pub fn name(&self) -> &PlSmallStr {
518 self.field.name()
519 }
520
521 #[inline(always)]
523 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 polars_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 polars_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> ChunkedArray<T>
1036where
1037 T: PolarsIntegerType,
1038 T::Native: Signed,
1039{
1040 pub fn has_negative(&self) -> bool {
1042 self.data_views().any(|values| {
1044 values
1045 .chunks(1024)
1046 .any(|block| block.iter().fold(false, |acc, v| acc | v.is_negative()))
1047 })
1048 }
1049}
1050
1051impl<T: PolarsDataType> Clone for ChunkedArray<T> {
1052 fn clone(&self) -> Self {
1053 ChunkedArray {
1054 field: self.field.clone(),
1055 chunks: self.chunks.clone(),
1056 flags: self.flags.clone(),
1057
1058 _pd: Default::default(),
1059 length: self.length,
1060 null_count: self.null_count,
1061 }
1062 }
1063}
1064
1065impl<T: PolarsDataType> AsRef<ChunkedArray<T>> for ChunkedArray<T> {
1066 fn as_ref(&self) -> &ChunkedArray<T> {
1067 self
1068 }
1069}
1070
1071impl ValueSize for ListChunked {
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
1079#[cfg(feature = "dtype-array")]
1080impl ValueSize for ArrayChunked {
1081 fn get_values_size(&self) -> usize {
1082 self.chunks
1083 .iter()
1084 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1085 }
1086}
1087impl ValueSize for StringChunked {
1088 fn get_values_size(&self) -> usize {
1089 self.chunks
1090 .iter()
1091 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1092 }
1093}
1094
1095impl ValueSize for BinaryOffsetChunked {
1096 fn get_values_size(&self) -> usize {
1097 self.chunks
1098 .iter()
1099 .fold(0usize, |acc, arr| acc + arr.get_values_size())
1100 }
1101}
1102
1103pub(crate) fn align_inner_chunks(
1108 chunk_lens: impl Iterator<Item = usize>,
1109 values: &Series,
1110) -> Series {
1111 let chunk_lens = chunk_lens.collect::<Vec<_>>();
1112
1113 if chunk_lens.len() == values.chunks().len()
1114 && chunk_lens
1115 .iter()
1116 .zip(values.chunks())
1117 .all(|(len, arr)| *len == arr.len())
1118 {
1119 return values.clone();
1120 }
1121
1122 let mut values = values.rechunk();
1123 let chunks = unsafe { values.chunks_mut() };
1124 let mut arr = chunks.pop().unwrap();
1125 chunks.extend(chunk_lens.into_iter().map(|len| {
1126 let chunk;
1127 (chunk, arr) = arr.split_at_boxed(len);
1128 chunk
1129 }));
1130 assert!(arr.is_empty());
1131 values
1132}
1133
1134pub(crate) fn to_primitive<T: PolarsNumericType>(
1135 values: Vec<T::Native>,
1136 validity: Option<Bitmap>,
1137) -> PrimitiveArray<T::Native> {
1138 PrimitiveArray::new(
1139 T::get_static_dtype().to_arrow(CompatLevel::newest()),
1140 values.into(),
1141 validity,
1142 )
1143}
1144
1145pub(crate) fn to_array<T: PolarsNumericType>(
1146 values: Vec<T::Native>,
1147 validity: Option<Bitmap>,
1148) -> ArrayRef {
1149 Box::new(to_primitive::<T>(values, validity))
1150}
1151
1152impl<T: PolarsDataType> Default for ChunkedArray<T> {
1153 fn default() -> Self {
1154 let dtype = T::get_static_dtype();
1155 let arrow_dtype = dtype.to_physical().to_arrow(CompatLevel::newest());
1156 ChunkedArray {
1157 field: Arc::new(Field::new(PlSmallStr::EMPTY, dtype)),
1158 chunks: vec![new_empty_array(arrow_dtype)],
1160 flags: StatisticsFlagsIM::empty(),
1161
1162 _pd: Default::default(),
1163 length: 0,
1164 null_count: 0,
1165 }
1166 }
1167}
1168
1169impl<T: PolarsDataType> BroadcastLength for ChunkedArray<T> {
1170 fn _broadcast_len(&self) -> usize {
1171 self.len()
1172 }
1173
1174 fn _column_name(&self) -> Option<&str> {
1175 Some(self.name())
1176 }
1177}
1178
1179#[cfg(test)]
1180pub(crate) mod test {
1181 use crate::prelude::*;
1182
1183 pub(crate) fn get_chunked_array() -> Int32Chunked {
1184 ChunkedArray::new(PlSmallStr::from_static("a"), &[1, 2, 3])
1185 }
1186
1187 #[test]
1188 fn test_sort() {
1189 let a = Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 9, 3, 2]);
1190 let b = a
1191 .sort(false)
1192 .iter()
1193 .map(|opt| opt.unwrap())
1194 .collect::<Vec<_>>();
1195 assert_eq!(b, [1, 2, 3, 9]);
1196 let a = StringChunked::new(PlSmallStr::from_static("a"), &["b", "a", "c"]);
1197 let a = a.sort(false);
1198 let b = a.iter().collect::<Vec<_>>();
1199 assert_eq!(b, [Some("a"), Some("b"), Some("c")]);
1200 assert!(a.is_sorted_ascending_flag());
1201 }
1202
1203 #[test]
1204 fn arithmetic() {
1205 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 6, 40]);
1206 let b = &Int32Chunked::new(PlSmallStr::from_static("b"), &[-1, 2, 3, 4]);
1207
1208 println!("{:?}", a + b);
1211 println!("{:?}", a - b);
1212 println!("{:?}", a * b);
1213 println!("{:?}", a / b);
1214 }
1215
1216 #[test]
1217 fn iter() {
1218 let s1 = get_chunked_array();
1219 assert_eq!(s1.iter().fold(0, |acc, val| { acc + val.unwrap() }), 6)
1221 }
1222
1223 #[test]
1224 fn limit() {
1225 let a = get_chunked_array();
1226 let b = a.limit(2);
1227 println!("{b:?}");
1228 assert_eq!(b.len(), 2)
1229 }
1230
1231 #[test]
1232 fn filter() {
1233 let a = get_chunked_array();
1234 let b = a
1235 .filter(&BooleanChunked::new(
1236 PlSmallStr::from_static("filter"),
1237 &[true, false, false],
1238 ))
1239 .unwrap();
1240 assert_eq!(b.len(), 1);
1241 assert_eq!(b.iter().next(), Some(Some(1)));
1242 }
1243
1244 #[test]
1245 fn aggregates() {
1246 let a = &Int32Chunked::new(PlSmallStr::from_static("a"), &[1, 100, 10, 9]);
1247 assert_eq!(a.max(), Some(100));
1248 assert_eq!(a.min(), Some(1));
1249 assert_eq!(a.sum(), Some(120))
1250 }
1251
1252 #[test]
1253 fn take() {
1254 let a = get_chunked_array();
1255 let new = a.take(&[0 as IdxSize, 1]).unwrap();
1256 assert_eq!(new.len(), 2)
1257 }
1258
1259 #[test]
1260 fn cast() {
1261 let a = get_chunked_array();
1262 let b = a.cast(&DataType::Int64).unwrap();
1263 assert_eq!(b.dtype(), &DataType::Int64)
1264 }
1265
1266 fn assert_slice_equal<T>(ca: &ChunkedArray<T>, eq: &[T::Native])
1267 where
1268 T: PolarsNumericType,
1269 {
1270 assert_eq!(ca.iter().map(|opt| opt.unwrap()).collect::<Vec<_>>(), eq)
1271 }
1272
1273 #[test]
1274 fn slice() {
1275 let mut first = UInt32Chunked::new(PlSmallStr::from_static("first"), &[0, 1, 2]);
1276 let second = UInt32Chunked::new(PlSmallStr::from_static("second"), &[3, 4, 5]);
1277 first.append(&second).unwrap();
1278 assert_slice_equal(&first.slice(0, 3), &[0, 1, 2]);
1279 assert_slice_equal(&first.slice(0, 4), &[0, 1, 2, 3]);
1280 assert_slice_equal(&first.slice(1, 4), &[1, 2, 3, 4]);
1281 assert_slice_equal(&first.slice(3, 2), &[3, 4]);
1282 assert_slice_equal(&first.slice(3, 3), &[3, 4, 5]);
1283 assert_slice_equal(&first.slice(-3, 3), &[3, 4, 5]);
1284 assert_slice_equal(&first.slice(-6, 6), &[0, 1, 2, 3, 4, 5]);
1285
1286 assert_eq!(first.slice(-7, 2).len(), 1);
1287 assert_eq!(first.slice(-3, 4).len(), 3);
1288 assert_eq!(first.slice(3, 4).len(), 3);
1289 assert_eq!(first.slice(10, 4).len(), 0);
1290 }
1291
1292 #[test]
1293 fn sorting() {
1294 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[9, 2, 4]);
1295 let sorted = s.sort(false);
1296 assert_slice_equal(&sorted, &[2, 4, 9]);
1297 let sorted = s.sort(true);
1298 assert_slice_equal(&sorted, &[9, 4, 2]);
1299
1300 let s: StringChunked = ["b", "a", "z"].iter().collect();
1301 let sorted = s.sort(false);
1302 assert_eq!(
1303 sorted.iter().collect::<Vec<_>>(),
1304 &[Some("a"), Some("b"), Some("z")]
1305 );
1306 let sorted = s.sort(true);
1307 assert_eq!(
1308 sorted.iter().collect::<Vec<_>>(),
1309 &[Some("z"), Some("b"), Some("a")]
1310 );
1311 let s: StringChunked = [Some("b"), None, Some("z")].iter().copied().collect();
1312 let sorted = s.sort(false);
1313 assert_eq!(
1314 sorted.iter().collect::<Vec<_>>(),
1315 &[None, Some("b"), Some("z")]
1316 );
1317 }
1318
1319 #[test]
1320 fn reverse() {
1321 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[1, 2, 3]);
1322 assert_slice_equal(&s.reverse(), &[3, 2, 1]);
1324 let s = UInt32Chunked::new(PlSmallStr::EMPTY, &[Some(1), None, Some(3)]);
1326 assert_eq!(Vec::from(&s.reverse()), &[Some(3), None, Some(1)]);
1327 let s = BooleanChunked::new(PlSmallStr::EMPTY, &[true, false]);
1328 assert_eq!(Vec::from(&s.reverse()), &[Some(false), Some(true)]);
1329
1330 let s = StringChunked::new(PlSmallStr::EMPTY, &["a", "b", "c"]);
1331 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), Some("b"), Some("a")]);
1332
1333 let s = StringChunked::new(PlSmallStr::EMPTY, &[Some("a"), None, Some("c")]);
1334 assert_eq!(Vec::from(&s.reverse()), &[Some("c"), None, Some("a")]);
1335 }
1336
1337 #[test]
1338 #[cfg(feature = "dtype-categorical")]
1339 fn test_iter_categorical() {
1340 let ca = StringChunked::new(
1341 PlSmallStr::EMPTY,
1342 &[Some("foo"), None, Some("bar"), Some("ham")],
1343 );
1344 let cats = Categories::new(
1345 PlSmallStr::EMPTY,
1346 PlSmallStr::EMPTY,
1347 CategoricalPhysical::U32,
1348 );
1349 let ca = ca.cast(&DataType::from_categories(cats)).unwrap();
1350 let ca = ca.cat32().unwrap();
1351 let v: Vec<_> = ca.physical().iter().collect();
1352 assert_eq!(v, &[Some(0), None, Some(1), Some(2)]);
1353 }
1354
1355 #[test]
1356 #[ignore]
1357 fn test_shrink_to_fit() {
1358 let mut builder = StringChunkedBuilder::new(PlSmallStr::from_static("foo"), 2048);
1359 builder.append_value("foo");
1360 let mut arr = builder.finish();
1361 let before = arr
1362 .chunks()
1363 .iter()
1364 .map(|arr| polars_arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1365 .sum::<usize>();
1366 arr.shrink_to_fit();
1367 let after = arr
1368 .chunks()
1369 .iter()
1370 .map(|arr| polars_arrow::compute::aggregate::estimated_bytes_size(arr.as_ref()))
1371 .sum::<usize>();
1372 assert!(before > after);
1373 }
1374}