polars_core/chunked_array/ops/
compare_inner.rs1#![allow(unsafe_op_in_unsafe_fn)]
2use std::cmp::Ordering;
5
6use crate::chunked_array::ChunkedArrayLayout;
7use crate::prelude::*;
8use crate::series::implementations::null::NullChunked;
9
10#[repr(transparent)]
11#[derive(Copy, Clone)]
12pub struct NonNull<T>(pub T);
13
14impl<T: TotalEq> TotalEq for NonNull<T> {
15 fn tot_eq(&self, other: &Self) -> bool {
16 self.0.tot_eq(&other.0)
17 }
18}
19
20pub trait NullOrderCmp {
21 fn null_order_cmp(&self, other: &Self, nulls_last: bool) -> Ordering;
22}
23
24impl<T: TotalOrd> NullOrderCmp for Option<T> {
25 fn null_order_cmp(&self, other: &Self, nulls_last: bool) -> Ordering {
26 match (self, other) {
27 (None, None) => Ordering::Equal,
28 (None, Some(_)) => {
29 if nulls_last {
30 Ordering::Greater
31 } else {
32 Ordering::Less
33 }
34 },
35 (Some(_), None) => {
36 if nulls_last {
37 Ordering::Less
38 } else {
39 Ordering::Greater
40 }
41 },
42 (Some(l), Some(r)) => l.tot_cmp(r),
43 }
44 }
45}
46
47impl<T: TotalOrd> NullOrderCmp for NonNull<T> {
48 fn null_order_cmp(&self, other: &Self, _nulls_last: bool) -> Ordering {
49 self.0.tot_cmp(&other.0)
50 }
51}
52
53trait GetInner {
54 type Item;
55 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item;
56}
57
58impl<'a, T: PolarsDataType> GetInner for &'a ChunkedArray<T> {
59 type Item = Option<T::Physical<'a>>;
60 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item {
61 ChunkedArray::get_unchecked(self, idx)
62 }
63}
64
65impl<'a, T: StaticArray> GetInner for &'a T {
66 type Item = Option<T::ValueT<'a>>;
67 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item {
68 <T as StaticArray>::get_unchecked(self, idx)
69 }
70}
71
72impl<'a, T: PolarsDataType> GetInner for NonNull<&'a ChunkedArray<T>> {
73 type Item = NonNull<T::Physical<'a>>;
74 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item {
75 NonNull(self.0.value_unchecked(idx))
76 }
77}
78
79impl<'a, T: StaticArray> GetInner for NonNull<&'a T> {
80 type Item = NonNull<T::ValueT<'a>>;
81 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item {
82 NonNull(self.0.value_unchecked(idx))
83 }
84}
85
86pub trait TotalEqInner: Send + Sync {
87 unsafe fn eq_element_unchecked(&self, idx_a: usize, idx_b: usize) -> bool;
90}
91
92impl<T> TotalEqInner for T
93where
94 T: GetInner + Send + Sync,
95 T::Item: TotalEq,
96{
97 #[inline]
98 unsafe fn eq_element_unchecked(&self, idx_a: usize, idx_b: usize) -> bool {
99 self.get_unchecked(idx_a).tot_eq(&self.get_unchecked(idx_b))
100 }
101}
102
103impl TotalEqInner for &NullChunked {
104 unsafe fn eq_element_unchecked(&self, _idx_a: usize, _idx_b: usize) -> bool {
105 true
106 }
107}
108
109pub(crate) trait IntoTotalEqInner<'a> {
111 fn into_total_eq_inner(self) -> Box<dyn TotalEqInner + 'a>;
113}
114
115impl<'a> IntoTotalEqInner<'a> for &'a NullChunked {
116 fn into_total_eq_inner(self) -> Box<dyn TotalEqInner + 'a> {
117 Box::new(self)
118 }
119}
120
121impl<'a, T> IntoTotalEqInner<'a> for &'a ChunkedArray<T>
123where
124 T: PolarsDataType,
125 T::Physical<'a>: TotalEq,
126{
127 fn into_total_eq_inner(self) -> Box<dyn TotalEqInner + 'a> {
128 match self.layout() {
129 ChunkedArrayLayout::SingleNoNull(arr) => Box::new(NonNull(arr)),
130 ChunkedArrayLayout::Single(arr) => Box::new(arr),
131 ChunkedArrayLayout::MultiNoNull(ca) => Box::new(NonNull(ca)),
132 ChunkedArrayLayout::Multi(ca) => Box::new(ca),
133 }
134 }
135}
136
137pub trait TotalOrdInner: Send + Sync {
138 unsafe fn cmp_element_unchecked(
141 &self,
142 idx_a: usize,
143 idx_b: usize,
144 nulls_last: bool,
145 ) -> Ordering;
146}
147
148impl<T> TotalOrdInner for T
149where
150 T: GetInner + Send + Sync,
151 T::Item: NullOrderCmp,
152{
153 #[inline]
154 unsafe fn cmp_element_unchecked(
155 &self,
156 idx_a: usize,
157 idx_b: usize,
158 nulls_last: bool,
159 ) -> Ordering {
160 let a = self.get_unchecked(idx_a);
161 let b = self.get_unchecked(idx_b);
162 a.null_order_cmp(&b, nulls_last)
163 }
164}
165
166impl TotalOrdInner for &NullChunked {
167 #[inline]
168 unsafe fn cmp_element_unchecked(
169 &self,
170 _idx_a: usize,
171 _idx_b: usize,
172 _nulls_last: bool,
173 ) -> Ordering {
174 Ordering::Equal
175 }
176}
177
178pub(crate) trait IntoTotalOrdInner<'a> {
180 fn into_total_ord_inner(self) -> Box<dyn TotalOrdInner + 'a>;
182}
183
184impl<'a, T> IntoTotalOrdInner<'a> for &'a ChunkedArray<T>
185where
186 T: PolarsDataType,
187 T::Physical<'a>: TotalOrd,
188{
189 fn into_total_ord_inner(self) -> Box<dyn TotalOrdInner + 'a> {
190 match self.layout() {
191 ChunkedArrayLayout::SingleNoNull(arr) => Box::new(NonNull(arr)),
192 ChunkedArrayLayout::Single(arr) => Box::new(arr),
193 ChunkedArrayLayout::MultiNoNull(ca) => Box::new(NonNull(ca)),
194 ChunkedArrayLayout::Multi(ca) => Box::new(ca),
195 }
196 }
197}
198
199impl<'a> IntoTotalOrdInner<'a> for &'a NullChunked {
200 fn into_total_ord_inner(self) -> Box<dyn TotalOrdInner + 'a> {
201 Box::new(self)
202 }
203}
204
205#[cfg(feature = "dtype-categorical")]
206struct LexicalCategorical<'a, T: PolarsCategoricalType> {
207 mapping: &'a CategoricalMapping,
208 cats: &'a ChunkedArray<T::PolarsPhysical>,
209}
210
211#[cfg(feature = "dtype-categorical")]
212impl<'a, T: PolarsCategoricalType> GetInner for LexicalCategorical<'a, T> {
213 type Item = Option<&'a str>;
214 unsafe fn get_unchecked(&self, idx: usize) -> Self::Item {
215 let cat = self.cats.get_unchecked(idx)?;
216 Some(self.mapping.cat_to_str_unchecked(cat.as_cat()))
217 }
218}
219
220#[cfg(feature = "dtype-categorical")]
221impl<'a, T: PolarsCategoricalType> IntoTotalOrdInner<'a> for &'a CategoricalChunked<T> {
222 fn into_total_ord_inner(self) -> Box<dyn TotalOrdInner + 'a> {
223 if self.uses_lexical_ordering() {
224 Box::new(LexicalCategorical::<T> {
225 mapping: self.get_mapping(),
226 cats: &self.phys,
227 })
228 } else {
229 self.phys.into_total_ord_inner()
230 }
231 }
232}