1use arrow::bitmap::bitmask::BitMask;
2
3use super::*;
4use crate::chunked_array::cast::CastOptions;
5use crate::chunked_array::{arg_max_bool, arg_min_bool};
6
7pub fn _agg_helper_idx_bool<F>(groups: &GroupsIdx, f: F) -> Series
8where
9 F: Fn((IdxSize, &IdxVec)) -> Option<bool> + Send + Sync,
10{
11 let ca: BooleanChunked = RAYON.install(|| groups.into_par_iter().map(f).collect());
12 ca.into_series()
13}
14
15pub fn _agg_helper_slice_bool<F>(groups: &[[IdxSize; 2]], f: F) -> Series
16where
17 F: Fn([IdxSize; 2]) -> Option<bool> + Send + Sync,
18{
19 let ca: BooleanChunked = RAYON.install(|| groups.par_iter().copied().map(f).collect());
20 ca.into_series()
21}
22
23#[cfg(feature = "bitwise")]
24impl BooleanChunked {
25 pub(crate) unsafe fn agg_and(&self, groups: &GroupsType) -> BooleanChunked {
26 self.agg_all(groups, true)
27 }
28
29 pub(crate) unsafe fn agg_or(&self, groups: &GroupsType) -> BooleanChunked {
30 self.agg_any(groups, true)
31 }
32
33 pub(crate) unsafe fn agg_xor(&self, groups: &GroupsType) -> BooleanChunked {
34 self.bool_agg(
35 groups,
36 true,
37 |values, idxs| {
38 idxs.iter()
39 .map(|i| {
40 <IdxSize as From<bool>>::from(unsafe {
41 values.get_bit_unchecked(*i as usize)
42 })
43 })
44 .sum::<IdxSize>()
45 % 2
46 == 1
47 },
48 |values, validity, idxs| {
49 idxs.iter()
50 .map(|i| {
51 <IdxSize as From<bool>>::from(unsafe {
52 validity.get_bit_unchecked(*i as usize)
53 & values.get_bit_unchecked(*i as usize)
54 })
55 })
56 .sum::<IdxSize>()
57 % 2
58 == 1
59 },
60 |_, _, _| unreachable!(),
61 |values, start, length| {
62 unsafe { values.sliced_unchecked(start as usize, length as usize) }.set_bits() % 2
63 == 1
64 },
65 |values, validity, start, length| {
66 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
67 let validity =
68 unsafe { validity.sliced_unchecked(start as usize, length as usize) };
69 values.num_intersections_with(validity) % 2 == 1
70 },
71 |_, _, _, _| unreachable!(),
72 )
73 }
74}
75
76impl BooleanChunked {
77 pub(crate) unsafe fn agg_min(&self, groups: &GroupsType) -> Series {
78 if !self.has_nulls() || matches!(groups, GroupsType::Slice { .. }) {
80 match self.is_sorted_flag() {
81 IsSorted::Ascending => {
82 return self.clone().into_series().agg_first_non_null(groups);
83 },
84 IsSorted::Descending => {
85 return self.clone().into_series().agg_last_non_null(groups);
86 },
87 _ => {},
88 }
89 }
90 let ca_self = self.rechunk();
91 let arr = ca_self.downcast_iter().next().unwrap();
92 let no_nulls = arr.null_count() == 0;
93 match groups {
94 GroupsType::Idx(groups) => _agg_helper_idx_bool(groups, |(first, idx)| {
95 debug_assert!(idx.len() <= self.len());
96 if idx.is_empty() {
97 None
98 } else if idx.len() == 1 {
99 arr.get(first as usize)
100 } else if no_nulls {
101 take_arg_min_bool_iter_unchecked_no_nulls(arr, idx2usize(idx))
102 .map(|p| arr.value_unchecked(idx[p] as usize))
103 } else {
104 take_arg_min_bool_iter_unchecked_nulls(arr, idx2usize(idx))
105 .map(|p| arr.value_unchecked(idx[p] as usize))
106 }
107 }),
108 GroupsType::Slice {
109 groups: groups_slice,
110 ..
111 } => _agg_helper_slice_bool(groups_slice, |[first, len]| {
112 debug_assert!(len <= self.len() as IdxSize);
113 match len {
114 0 => None,
115 1 => self.get(first as usize),
116 _ => {
117 let arr_group = _slice_from_offsets(self, first, len);
118 arr_group.min()
119 },
120 }
121 }),
122 }
123 }
124 pub(crate) unsafe fn agg_max(&self, groups: &GroupsType) -> Series {
125 if !self.has_nulls() || matches!(groups, GroupsType::Slice { .. }) {
127 match self.is_sorted_flag() {
128 IsSorted::Ascending => return self.clone().into_series().agg_last_non_null(groups),
129 IsSorted::Descending => {
130 return self.clone().into_series().agg_first_non_null(groups);
131 },
132 _ => {},
133 }
134 }
135
136 let ca_self = self.rechunk();
137 let arr = ca_self.downcast_iter().next().unwrap();
138 let no_nulls = arr.null_count() == 0;
139 match groups {
140 GroupsType::Idx(groups) => _agg_helper_idx_bool(groups, |(first, idx)| {
141 debug_assert!(idx.len() <= self.len());
142 if idx.is_empty() {
143 None
144 } else if idx.len() == 1 {
145 self.get(first as usize)
146 } else if no_nulls {
147 take_arg_max_bool_iter_unchecked_no_nulls(arr, idx2usize(idx))
148 .map(|p| arr.value_unchecked(idx[p] as usize))
149 } else {
150 take_arg_max_bool_iter_unchecked_nulls(arr, idx2usize(idx))
151 .map(|p| arr.value_unchecked(idx[p] as usize))
152 }
153 }),
154 GroupsType::Slice {
155 groups: groups_slice,
156 ..
157 } => _agg_helper_slice_bool(groups_slice, |[first, len]| {
158 debug_assert!(len <= self.len() as IdxSize);
159 match len {
160 0 => None,
161 1 => self.get(first as usize),
162 _ => {
163 let arr_group = _slice_from_offsets(self, first, len);
164 arr_group.max()
165 },
166 }
167 }),
168 }
169 }
170
171 pub(crate) unsafe fn agg_arg_min(&self, groups: &GroupsType) -> Series {
172 if !self.has_nulls() || matches!(groups, GroupsType::Slice { .. }) {
174 match self.is_sorted_flag() {
175 IsSorted::Ascending => {
176 return self.clone().into_series().agg_arg_first_non_null(groups);
177 },
178 IsSorted::Descending => {
179 return self.clone().into_series().agg_arg_last_non_null(groups);
180 },
181 _ => {},
182 }
183 }
184
185 let ca_self = self.rechunk();
186 let arr = ca_self.downcast_iter().next().unwrap();
187 let no_nulls = arr.null_count() == 0;
188 match groups {
189 GroupsType::Idx(groups) => agg_helper_idx_on_all::<IdxType, _>(groups, |idx| {
190 debug_assert!(idx.len() <= ca_self.len());
191 if idx.is_empty() {
192 None
193 } else if idx.len() == 1 {
194 arr.get(idx[0] as usize).map(|_| 0)
195 } else if no_nulls {
196 take_arg_min_bool_iter_unchecked_no_nulls(arr, idx2usize(idx))
197 .map(|p| p as IdxSize)
198 } else {
199 take_arg_min_bool_iter_unchecked_nulls(arr, idx2usize(idx))
200 .map(|p| p as IdxSize)
201 }
202 }),
203 GroupsType::Slice {
204 groups: groups_slice,
205 ..
206 } => _agg_helper_slice::<IdxType, _>(groups_slice, |[first, len]| {
207 debug_assert!(len <= self.len() as IdxSize);
208 match len {
209 0 => None,
210 1 => self.get(first as usize).map(|_| 0),
211 _ => {
212 let group_ca = _slice_from_offsets(self, first, len);
213 arg_min_bool(&group_ca).map(|p| p as IdxSize)
214 },
215 }
216 }),
217 }
218 }
219
220 pub(crate) unsafe fn agg_arg_max(&self, groups: &GroupsType) -> Series {
221 if !self.has_nulls() || matches!(groups, GroupsType::Slice { .. }) {
223 match self.is_sorted_flag() {
224 IsSorted::Ascending => {
225 return self.clone().into_series().agg_arg_last_non_null(groups);
226 },
227 IsSorted::Descending => {
228 return self.clone().into_series().agg_arg_first_non_null(groups);
229 },
230 _ => {},
231 }
232 }
233
234 let ca_self = self.rechunk();
235 let arr = ca_self.downcast_iter().next().unwrap();
236 let no_nulls = arr.null_count() == 0;
237 match groups {
238 GroupsType::Idx(groups) => agg_helper_idx_on_all::<IdxType, _>(groups, |idx| {
239 debug_assert!(idx.len() <= ca_self.len());
240 if idx.is_empty() {
241 None
242 } else if idx.len() == 1 {
243 arr.get(idx[0] as usize).map(|_| 0)
244 } else if no_nulls {
245 take_arg_max_bool_iter_unchecked_no_nulls(arr, idx2usize(idx))
246 .map(|p| p as IdxSize)
247 } else {
248 take_arg_max_bool_iter_unchecked_nulls(arr, idx2usize(idx))
249 .map(|p| p as IdxSize)
250 }
251 }),
252 GroupsType::Slice {
253 groups: groups_slice,
254 ..
255 } => _agg_helper_slice::<IdxType, _>(groups_slice, |[first, len]| {
256 debug_assert!(len <= self.len() as IdxSize);
257 match len {
258 0 => None,
259 1 => self.get(first as usize).map(|_| 0),
260 _ => {
261 let group_ca = _slice_from_offsets(self, first, len);
262 arg_max_bool(&group_ca).map(|p| p as IdxSize)
263 },
264 }
265 }),
266 }
267 }
268
269 pub(crate) unsafe fn agg_sum(&self, groups: &GroupsType) -> Series {
270 self.cast_with_options(&IDX_DTYPE, CastOptions::Overflowing)
271 .unwrap()
272 .agg_sum(groups)
273 }
274
275 #[expect(clippy::too_many_arguments)]
279 unsafe fn bool_agg(
280 &self,
281 groups: &GroupsType,
282 ignore_nulls: bool,
283
284 idx_no_valid: impl Fn(BitMask, &[IdxSize]) -> bool + Send + Sync,
285 idx_validity: impl Fn(BitMask, BitMask, &[IdxSize]) -> bool + Send + Sync,
286 idx_kleene: impl Fn(BitMask, BitMask, &[IdxSize]) -> Option<bool> + Send + Sync,
287
288 slice_no_valid: impl Fn(BitMask, IdxSize, IdxSize) -> bool + Send + Sync,
289 slice_validity: impl Fn(BitMask, BitMask, IdxSize, IdxSize) -> bool + Send + Sync,
290 slice_kleene: impl Fn(BitMask, BitMask, IdxSize, IdxSize) -> Option<bool> + Send + Sync,
291 ) -> BooleanChunked {
292 let name = self.name().clone();
293 let values = self.rechunk();
294 let values = values.downcast_as_array();
295
296 let groups_len = groups.len();
297
298 RAYON.install(|| {
299 let validity = values
300 .validity()
301 .filter(|v| v.unset_bits() > 0)
302 .map(BitMask::from_bitmap);
303 let values = BitMask::from_bitmap(values.values());
304
305 if !ignore_nulls && let Some(validity) = validity {
306 match groups {
307 GroupsType::Idx(idx) => {
308 let all = idx.all();
309 collect_bool_opt_par(name, groups_len, |g| {
310 idx_kleene(values, validity, &all[g])
311 })
312 },
313 GroupsType::Slice { groups, .. } => {
314 collect_bool_opt_par(name, groups_len, |g| {
315 let [s, l] = groups[g];
316 slice_kleene(values, validity, s, l)
317 })
318 },
319 }
320 } else {
321 match groups {
322 GroupsType::Idx(idx) => {
323 let all = idx.all();
324 match validity {
325 None => collect_bool_par(name, groups_len, |g| {
326 idx_no_valid(values, &all[g])
327 }),
328 Some(validity) => collect_bool_par(name, groups_len, |g| {
329 idx_validity(values, validity, &all[g])
330 }),
331 }
332 },
333 GroupsType::Slice { groups, .. } => match validity {
334 None => collect_bool_par(name, groups_len, |g| {
335 let [s, l] = groups[g];
336 slice_no_valid(values, s, l)
337 }),
338 Some(validity) => collect_bool_par(name, groups_len, |g| {
339 let [s, l] = groups[g];
340 slice_validity(values, validity, s, l)
341 }),
342 },
343 }
344 }
345 })
346 }
347
348 pub unsafe fn agg_any(&self, groups: &GroupsType, ignore_nulls: bool) -> BooleanChunked {
352 self.bool_agg(
353 groups,
354 ignore_nulls,
355 |values, idxs| {
356 idxs.iter()
357 .any(|i| unsafe { values.get_bit_unchecked(*i as usize) })
358 },
359 |values, validity, idxs| {
360 idxs.iter().any(|i| unsafe {
361 validity.get_bit_unchecked(*i as usize) & values.get_bit_unchecked(*i as usize)
362 })
363 },
364 |values, validity, idxs| {
365 let mut saw_null = false;
366 for i in idxs.iter() {
367 let is_valid = unsafe { validity.get_bit_unchecked(*i as usize) };
368 let is_true = unsafe { values.get_bit_unchecked(*i as usize) };
369
370 if is_valid & is_true {
371 return Some(true);
372 }
373 saw_null |= !is_valid;
374 }
375 (!saw_null).then_some(false)
376 },
377 |values, start, length| {
378 unsafe { values.sliced_unchecked(start as usize, length as usize) }.leading_zeros()
379 < length as usize
380 },
381 |values, validity, start, length| {
382 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
383 let validity =
384 unsafe { validity.sliced_unchecked(start as usize, length as usize) };
385 values.intersects_with(validity)
386 },
387 |values, validity, start, length| {
388 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
389 let validity =
390 unsafe { validity.sliced_unchecked(start as usize, length as usize) };
391
392 if values.intersects_with(validity) {
393 Some(true)
394 } else if validity.unset_bits() == 0 {
395 Some(false)
396 } else {
397 None
398 }
399 },
400 )
401 }
402
403 pub unsafe fn agg_all(&self, groups: &GroupsType, ignore_nulls: bool) -> BooleanChunked {
407 self.bool_agg(
408 groups,
409 ignore_nulls,
410 |values, idxs| {
411 idxs.iter()
412 .all(|i| unsafe { values.get_bit_unchecked(*i as usize) })
413 },
414 |values, validity, idxs| {
415 idxs.iter().all(|i| unsafe {
416 !validity.get_bit_unchecked(*i as usize) | values.get_bit_unchecked(*i as usize)
417 })
418 },
419 |values, validity, idxs| {
420 let mut saw_null = false;
421 for i in idxs.iter() {
422 let is_valid = unsafe { validity.get_bit_unchecked(*i as usize) };
423 let is_true = unsafe { values.get_bit_unchecked(*i as usize) };
424
425 if is_valid & !is_true {
426 return Some(false);
427 }
428 saw_null |= !is_valid;
429 }
430 (!saw_null).then_some(true)
431 },
432 |values, start, length| {
433 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
434 values.unset_bits() == 0
435 },
436 |values, validity, start, length| {
437 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
438 let validity =
439 unsafe { validity.sliced_unchecked(start as usize, length as usize) };
440 values.num_intersections_with(validity) == validity.set_bits()
441 },
442 |values, validity, start, length| {
443 let values = unsafe { values.sliced_unchecked(start as usize, length as usize) };
444 let validity =
445 unsafe { validity.sliced_unchecked(start as usize, length as usize) };
446
447 let num_non_nulls = validity.set_bits();
448
449 if values.num_intersections_with(validity) < num_non_nulls {
450 Some(false)
451 } else if num_non_nulls < values.len() {
452 None
453 } else {
454 Some(true)
455 }
456 },
457 )
458 }
459}
460
461pub fn collect_bool_par<F>(name: PlSmallStr, len: usize, f: F) -> BooleanChunked
462where
463 F: Fn(usize) -> bool + Send + Sync,
464{
465 let n_bytes = len.div_ceil(8);
466 let mut values: Vec<u8> = Vec::with_capacity(n_bytes);
467
468 (0..n_bytes)
469 .into_par_iter()
470 .map(|b| {
471 let lo = b * 8;
472 let hi = (lo + 8).min(len);
473 let mut v = 0u8;
474 for (bit, g) in (lo..hi).enumerate() {
475 v |= (f(g) as u8) << bit;
476 }
477 v
478 })
479 .collect_into_vec(&mut values);
480
481 BooleanChunked::from_bitmap(name, Bitmap::from_u8_vec(values, len))
482}
483
484pub fn collect_bool_opt_par<F>(name: PlSmallStr, len: usize, f: F) -> BooleanChunked
485where
486 F: Fn(usize) -> Option<bool> + Send + Sync,
487{
488 let n_bytes = len.div_ceil(8);
489 let mut values: Vec<u8> = Vec::with_capacity(n_bytes);
490 let mut validity: Vec<u8> = Vec::with_capacity(n_bytes);
491
492 (0..n_bytes)
493 .into_par_iter()
494 .map(|b| {
495 let lo = b * 8;
496 let hi = (lo + 8).min(len);
497 let (mut v, mut m) = (0u8, 0u8);
498 for (bit, g) in (lo..hi).enumerate() {
499 if let Some(x) = f(g) {
500 m |= 1 << bit;
501 v |= (x as u8) << bit;
502 }
503 }
504 (v, m)
505 })
506 .unzip_into_vecs(&mut values, &mut validity);
507
508 let values = Bitmap::from_u8_vec(values, len);
509 let validity = Bitmap::from_u8_vec(validity, len);
510 let validity = (validity.unset_bits() > 0).then_some(validity);
511 BooleanChunked::with_chunk(
512 name,
513 BooleanArray::new(ArrowDataType::Boolean, values, validity),
514 )
515}