1use std::hash::Hash;
7
8use num_traits::PrimInt;
9use rustc_hash::FxBuildHasher;
10use vortex_array::ExecutionCtx;
11use vortex_array::arrays::PrimitiveArray;
12use vortex_array::arrays::primitive::NativeValue;
13use vortex_array::dtype::IntegerPType;
14use vortex_array::expr::stats::Stat;
15use vortex_array::match_each_integer_ptype;
16use vortex_array::scalar::PValue;
17use vortex_array::scalar::Scalar;
18use vortex_error::VortexError;
19use vortex_error::VortexExpect;
20use vortex_error::VortexResult;
21use vortex_mask::AllOr;
22use vortex_utils::aliases::hash_map::HashMap;
23
24use super::GenerateStatsOptions;
25
26#[derive(Debug, Clone)]
28pub struct DistinctInfo<T> {
29 distinct_values: HashMap<NativeValue<T>, u32, FxBuildHasher>,
31 distinct_count: u32,
33 most_frequent_value: T,
35 top_frequency: u32,
37}
38
39impl<T> DistinctInfo<T> {
40 pub fn distinct_values(&self) -> &HashMap<NativeValue<T>, u32, FxBuildHasher> {
42 &self.distinct_values
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct TypedStats<T> {
49 min: T,
51 max: T,
53 distinct: Option<DistinctInfo<T>>,
55}
56
57impl<T> TypedStats<T> {
58 pub fn distinct(&self) -> Option<&DistinctInfo<T>> {
60 self.distinct.as_ref()
61 }
62}
63
64impl<T> TypedStats<T> {
65 fn distinct_count(&self) -> Option<u32> {
67 Some(self.distinct.as_ref()?.distinct_count)
68 }
69
70 fn most_frequent_value_and_count(&self) -> Option<(&T, u32)> {
72 let distinct = self.distinct.as_ref()?;
73 Some((&distinct.most_frequent_value, distinct.top_frequency))
74 }
75}
76
77#[derive(Clone, Debug)]
82pub enum ErasedStats {
83 U8(TypedStats<u8>),
85 U16(TypedStats<u16>),
87 U32(TypedStats<u32>),
89 U64(TypedStats<u64>),
91 I8(TypedStats<i8>),
93 I16(TypedStats<i16>),
95 I32(TypedStats<i32>),
97 I64(TypedStats<i64>),
99}
100
101impl ErasedStats {
102 pub fn min_is_zero(&self) -> bool {
104 match &self {
105 ErasedStats::U8(x) => x.min == 0,
106 ErasedStats::U16(x) => x.min == 0,
107 ErasedStats::U32(x) => x.min == 0,
108 ErasedStats::U64(x) => x.min == 0,
109 ErasedStats::I8(x) => x.min == 0,
110 ErasedStats::I16(x) => x.min == 0,
111 ErasedStats::I32(x) => x.min == 0,
112 ErasedStats::I64(x) => x.min == 0,
113 }
114 }
115
116 pub fn min_is_negative(&self) -> bool {
118 match &self {
119 ErasedStats::U8(_)
120 | ErasedStats::U16(_)
121 | ErasedStats::U32(_)
122 | ErasedStats::U64(_) => false,
123 ErasedStats::I8(x) => x.min < 0,
124 ErasedStats::I16(x) => x.min < 0,
125 ErasedStats::I32(x) => x.min < 0,
126 ErasedStats::I64(x) => x.min < 0,
127 }
128 }
129
130 pub fn max_minus_min(&self) -> u64 {
132 match &self {
133 ErasedStats::U8(x) => (x.max - x.min) as u64,
134 ErasedStats::U16(x) => (x.max - x.min) as u64,
135 ErasedStats::U32(x) => (x.max - x.min) as u64,
136 ErasedStats::U64(x) => x.max - x.min,
137 ErasedStats::I8(x) => (x.max as i16 - x.min as i16) as u64,
138 ErasedStats::I16(x) => (x.max as i32 - x.min as i32) as u64,
139 ErasedStats::I32(x) => (x.max as i64 - x.min as i64) as u64,
140 ErasedStats::I64(x) => u64::try_from(x.max as i128 - x.min as i128)
141 .vortex_expect("max minus min result bigger than u64"),
142 }
143 }
144
145 pub fn max_ilog2(&self) -> Option<u32> {
154 match &self {
155 ErasedStats::U8(x) => x.max.checked_ilog2(),
156 ErasedStats::U16(x) => x.max.checked_ilog2(),
157 ErasedStats::U32(x) => x.max.checked_ilog2(),
158 ErasedStats::U64(x) => x.max.checked_ilog2(),
159 ErasedStats::I8(x) => (x.max as u8).checked_ilog2(),
161 ErasedStats::I16(x) => (x.max as u16).checked_ilog2(),
162 ErasedStats::I32(x) => (x.max as u32).checked_ilog2(),
163 ErasedStats::I64(x) => (x.max as u64).checked_ilog2(),
164 }
165 }
166
167 pub fn distinct_count(&self) -> Option<u32> {
169 match &self {
170 ErasedStats::U8(x) => x.distinct_count(),
171 ErasedStats::U16(x) => x.distinct_count(),
172 ErasedStats::U32(x) => x.distinct_count(),
173 ErasedStats::U64(x) => x.distinct_count(),
174 ErasedStats::I8(x) => x.distinct_count(),
175 ErasedStats::I16(x) => x.distinct_count(),
176 ErasedStats::I32(x) => x.distinct_count(),
177 ErasedStats::I64(x) => x.distinct_count(),
178 }
179 }
180
181 pub fn most_frequent_value_and_count(&self) -> Option<(PValue, u32)> {
183 match &self {
184 ErasedStats::U8(x) => {
185 let (top_value, count) = x.most_frequent_value_and_count()?;
186 Some(((*top_value).into(), count))
187 }
188 ErasedStats::U16(x) => {
189 let (top_value, count) = x.most_frequent_value_and_count()?;
190 Some(((*top_value).into(), count))
191 }
192 ErasedStats::U32(x) => {
193 let (top_value, count) = x.most_frequent_value_and_count()?;
194 Some(((*top_value).into(), count))
195 }
196 ErasedStats::U64(x) => {
197 let (top_value, count) = x.most_frequent_value_and_count()?;
198 Some(((*top_value).into(), count))
199 }
200 ErasedStats::I8(x) => {
201 let (top_value, count) = x.most_frequent_value_and_count()?;
202 Some(((*top_value).into(), count))
203 }
204 ErasedStats::I16(x) => {
205 let (top_value, count) = x.most_frequent_value_and_count()?;
206 Some(((*top_value).into(), count))
207 }
208 ErasedStats::I32(x) => {
209 let (top_value, count) = x.most_frequent_value_and_count()?;
210 Some(((*top_value).into(), count))
211 }
212 ErasedStats::I64(x) => {
213 let (top_value, count) = x.most_frequent_value_and_count()?;
214 Some(((*top_value).into(), count))
215 }
216 }
217 }
218}
219
220macro_rules! impl_from_typed {
222 ($T:ty, $variant:path) => {
223 impl From<TypedStats<$T>> for ErasedStats {
224 fn from(typed: TypedStats<$T>) -> Self {
225 $variant(typed)
226 }
227 }
228 };
229}
230
231impl_from_typed!(u8, ErasedStats::U8);
232impl_from_typed!(u16, ErasedStats::U16);
233impl_from_typed!(u32, ErasedStats::U32);
234impl_from_typed!(u64, ErasedStats::U64);
235impl_from_typed!(i8, ErasedStats::I8);
236impl_from_typed!(i16, ErasedStats::I16);
237impl_from_typed!(i32, ErasedStats::I32);
238impl_from_typed!(i64, ErasedStats::I64);
239
240#[derive(Clone, Debug)]
242pub struct IntegerStats {
243 null_count: u32,
245 value_count: u32,
247 average_run_length: u32,
249 erased: ErasedStats,
251}
252
253impl IntegerStats {
254 fn generate_opts_fallible(
256 input: &PrimitiveArray,
257 opts: GenerateStatsOptions,
258 ctx: &mut ExecutionCtx,
259 ) -> VortexResult<Self> {
260 match_each_integer_ptype!(input.ptype(), |T| {
261 typed_int_stats::<T>(input, opts.count_distinct_values, ctx)
262 })
263 }
264
265 pub fn distinct_count(&self) -> Option<u32> {
267 self.erased.distinct_count()
268 }
269
270 pub fn most_frequent_value_and_count(&self) -> Option<(PValue, u32)> {
272 self.erased.most_frequent_value_and_count()
273 }
274}
275
276impl IntegerStats {
277 pub fn generate(input: &PrimitiveArray, ctx: &mut ExecutionCtx) -> Self {
279 Self::generate_opts(input, GenerateStatsOptions::default(), ctx)
280 }
281
282 pub fn generate_opts(
284 input: &PrimitiveArray,
285 opts: GenerateStatsOptions,
286 ctx: &mut ExecutionCtx,
287 ) -> Self {
288 Self::generate_opts_fallible(input, opts, ctx)
289 .vortex_expect("IntegerStats::generate_opts should not fail")
290 }
291
292 pub fn null_count(&self) -> u32 {
294 self.null_count
295 }
296
297 pub fn value_count(&self) -> u32 {
299 self.value_count
300 }
301
302 pub fn average_run_length(&self) -> u32 {
304 self.average_run_length
305 }
306
307 pub fn erased(&self) -> &ErasedStats {
309 &self.erased
310 }
311}
312
313fn typed_int_stats<T>(
315 array: &PrimitiveArray,
316 count_distinct_values: bool,
317 ctx: &mut ExecutionCtx,
318) -> VortexResult<IntegerStats>
319where
320 T: IntegerPType + PrimInt + for<'a> TryFrom<&'a Scalar, Error = VortexError>,
321 TypedStats<T>: Into<ErasedStats>,
322 NativeValue<T>: Eq + Hash,
323{
324 if array.is_empty() {
326 return Ok(IntegerStats {
327 null_count: 0,
328 value_count: 0,
329 average_run_length: 0,
330 erased: TypedStats {
331 min: T::max_value(),
332 max: T::min_value(),
333 distinct: None,
334 }
335 .into(),
336 });
337 }
338
339 if array.all_invalid(ctx)? {
340 return Ok(IntegerStats {
341 null_count: u32::try_from(array.len())?,
342 value_count: 0,
343 average_run_length: 0,
344 erased: TypedStats {
345 min: T::max_value(),
346 max: T::min_value(),
347 distinct: Some(DistinctInfo {
348 distinct_values: HashMap::with_capacity_and_hasher(0, FxBuildHasher),
349 distinct_count: 0,
350 most_frequent_value: T::zero(),
351 top_frequency: 0,
352 }),
353 }
354 .into(),
355 });
356 }
357
358 let validity = array
359 .as_ref()
360 .validity()?
361 .execute_mask(array.as_ref().len(), ctx)?;
362 let null_count = validity.false_count();
363 let value_count = validity.true_count();
364
365 let array_ref = array.as_ref();
366 let min = array_ref
367 .statistics()
368 .compute_as::<T>(Stat::Min, ctx)
369 .vortex_expect("min should be computed");
370
371 let max = array_ref
372 .statistics()
373 .compute_as::<T>(Stat::Max, ctx)
374 .vortex_expect("max should be computed");
375
376 let head_idx = validity
378 .first()
379 .vortex_expect("All null masks have been handled before");
380 let buffer = array.to_buffer::<T>();
381 let head = buffer[head_idx];
382
383 let mut loop_state = LoopState {
384 distinct: if count_distinct_values {
385 DistinctCounter::new(min, max, array.len())
386 } else {
387 DistinctCounter::Off
388 },
389 prev: head,
390 pending: 0,
391 runs: 1,
392 };
393
394 let sliced = buffer.slice(head_idx..array.len());
395 let (chunks, remainder) = sliced.as_slice().as_chunks::<64>();
396 let remainder_valid = match validity.bit_buffer() {
397 AllOr::All => {
398 for chunk in chunks {
399 inner_loop_nonnull(chunk, count_distinct_values, &mut loop_state)
400 }
401 (1u64 << remainder.len()) - 1
402 }
403 AllOr::None => unreachable!("All invalid arrays have been handled before"),
404 AllOr::Some(v) => {
405 let mask = v.slice(head_idx..array.len());
406 let words = mask.chunks();
407 for (chunk, valid) in chunks.iter().zip(words.iter()) {
409 match valid {
410 0 => {}
412 u64::MAX => inner_loop_nonnull(chunk, count_distinct_values, &mut loop_state),
414 _ => inner_loop_nullable(chunk, count_distinct_values, valid, &mut loop_state),
416 }
417 }
418 words.remainder_bits()
419 }
420 };
421
422 if remainder_valid != 0 {
424 let mut last = [loop_state.prev; 64];
425 last[..remainder.len()].copy_from_slice(remainder);
426 inner_loop_nullable(
427 &last,
428 count_distinct_values,
429 remainder_valid,
430 &mut loop_state,
431 );
432 }
433
434 if count_distinct_values {
435 loop_state.flush();
436 }
437
438 let runs = loop_state.runs;
439 let distinct = loop_state.distinct.finish();
440
441 let typed = TypedStats { min, max, distinct };
442
443 let null_count = u32::try_from(null_count)?;
444 let value_count = u32::try_from(value_count)?;
445
446 Ok(IntegerStats {
447 null_count,
448 value_count,
449 average_run_length: value_count / runs,
450 erased: typed.into(),
451 })
452}
453
454const DENSE_DISTINCT_MAX_RANGE: usize = 1 << 16;
456
457const DENSE_DISTINCT_ALWAYS_RANGE: usize = 1 << 8;
460
461enum DistinctCounter<T> {
463 Off,
465 Dense {
468 min: T,
470 min_index: usize,
473 counts: Vec<u32>,
475 },
476 Hashed(HashMap<NativeValue<T>, u32, FxBuildHasher>),
478}
479
480impl<T: IntegerPType> DistinctCounter<T>
481where
482 NativeValue<T>: Eq + Hash,
483{
484 fn new(min: T, max: T, len: usize) -> Self {
486 let range_len = match (min.to_i128(), max.to_i128()) {
487 (Some(min), Some(max)) => usize::try_from(max - min + 1).unwrap_or(usize::MAX),
488 _ => usize::MAX,
489 };
490
491 if range_len <= DENSE_DISTINCT_ALWAYS_RANGE
493 || (range_len <= DENSE_DISTINCT_MAX_RANGE && range_len <= len)
494 {
495 Self::Dense {
496 min,
497 min_index: min.as_(),
498 counts: vec![0; range_len],
499 }
500 } else {
501 Self::Hashed(HashMap::with_capacity_and_hasher(len / 2, FxBuildHasher))
502 }
503 }
504
505 #[allow(clippy::inline_always)]
507 #[inline(always)]
508 fn add(&mut self, value: T, count: u32) {
509 match self {
510 Self::Off => {}
511 Self::Dense {
512 min_index, counts, ..
513 } => counts[value.as_().wrapping_sub(*min_index)] += count,
514 Self::Hashed(distinct_values) => {
515 *distinct_values.entry(NativeValue(value)).or_insert(0) += count
516 }
517 }
518 }
519
520 fn finish(self) -> Option<DistinctInfo<T>> {
522 let distinct_values: HashMap<NativeValue<T>, u32, FxBuildHasher> = match self {
523 Self::Off => return None,
524 Self::Dense { min, counts, .. } => {
525 let min = min.to_i128().vortex_expect("integers fit in i128");
526 counts
527 .iter()
528 .enumerate()
529 .filter(|&(_, &count)| count > 0)
530 .map(|(index, &count)| {
531 let value = <T as num_traits::NumCast>::from(min + index as i128)
532 .vortex_expect("values between min and max fit in the type");
533 (NativeValue(value), count)
534 })
535 .collect()
536 }
537 Self::Hashed(distinct_values) => distinct_values,
538 };
539
540 let (&top_value, &top_count) = distinct_values
541 .iter()
542 .max_by_key(|&(_, &count)| count)
543 .vortex_expect("we know this is non-empty");
544
545 Some(DistinctInfo {
546 distinct_count: u32::try_from(distinct_values.len())
547 .vortex_expect("there are more than `u32::MAX` distinct values"),
548 most_frequent_value: top_value.0,
549 top_frequency: top_count,
550 distinct_values,
551 })
552 }
553}
554
555struct LoopState<T> {
557 prev: T,
559 pending: u32,
561 runs: u32,
563 distinct: DistinctCounter<T>,
565}
566
567impl<T: IntegerPType> LoopState<T>
568where
569 NativeValue<T>: Eq + Hash,
570{
571 #[allow(clippy::inline_always)]
573 #[inline(always)]
574 fn flush(&mut self) {
575 self.distinct.add(self.prev, self.pending);
576 self.pending = 0;
577 }
578
579 #[allow(clippy::inline_always)]
581 #[inline(always)]
582 fn push<const COUNT_DISTINCT_VALUES: bool>(&mut self, value: T) {
583 if value != self.prev {
584 if COUNT_DISTINCT_VALUES {
585 self.flush();
586 }
587 self.prev = value;
588 self.runs += 1;
589 }
590 if COUNT_DISTINCT_VALUES {
591 self.pending += 1;
592 }
593 }
594}
595
596#[allow(clippy::inline_always)]
598#[inline(always)]
599fn inner_loop_nonnull<T: IntegerPType>(
600 values: &[T; 64],
601 count_distinct_values: bool,
602 state: &mut LoopState<T>,
603) where
604 NativeValue<T>: Eq + Hash,
605{
606 if count_distinct_values {
607 inner_loop_nonnull_impl::<T, true>(values, state);
608 } else {
609 inner_loop_nonnull_impl::<T, false>(values, state);
610 }
611}
612
613#[allow(clippy::inline_always)]
615#[inline(always)]
616fn inner_loop_nonnull_impl<T: IntegerPType, const COUNT_DISTINCT_VALUES: bool>(
617 values: &[T; 64],
618 state: &mut LoopState<T>,
619) where
620 NativeValue<T>: Eq + Hash,
621{
622 let transitions = u8::from(values[0] != state.prev)
625 + values
626 .iter()
627 .zip(&values[1..])
628 .map(|(a, b)| u8::from(a != b))
629 .sum::<u8>();
630
631 if COUNT_DISTINCT_VALUES {
632 if transitions == 0 {
633 state.pending += 64;
634 return;
635 }
636 for &value in values {
637 if value != state.prev {
638 state.flush();
639 state.prev = value;
640 }
641 state.pending += 1;
642 }
643 }
644
645 state.runs += u32::from(transitions);
646 state.prev = values[63];
647}
648
649#[allow(clippy::inline_always)]
651#[inline(always)]
652fn inner_loop_nullable<T: IntegerPType>(
653 values: &[T; 64],
654 count_distinct_values: bool,
655 valid: u64,
656 state: &mut LoopState<T>,
657) where
658 NativeValue<T>: Eq + Hash,
659{
660 if count_distinct_values {
661 let mut valid = valid;
663 while valid != 0 {
664 state.push::<true>(values[valid.trailing_zeros() as usize]);
665 valid &= valid - 1;
666 }
667 } else {
668 inner_loop_nonnull_impl::<T, false>(&forward_fill(values, valid, state.prev), state);
671 }
672}
673
674#[allow(clippy::inline_always)]
678#[inline(always)]
679fn forward_fill<T: Copy>(values: &[T; 64], valid: u64, prev: T) -> [T; 64] {
680 let mut filled = *values;
681 let mut nulls = !valid;
682 while nulls != 0 {
683 let i = nulls.trailing_zeros() as usize;
684 filled[i] = if i == 0 { prev } else { filled[i - 1] };
685 nulls &= nulls - 1;
686 }
687 filled
688}
689
690#[cfg(test)]
691mod tests {
692 use std::iter;
693
694 use rstest::rstest;
695 use vortex_array::VortexSessionExecute;
696 use vortex_array::array_session;
697 use vortex_array::arrays::PrimitiveArray;
698 use vortex_array::validity::Validity;
699 use vortex_buffer::BitBuffer;
700 use vortex_buffer::Buffer;
701 use vortex_buffer::buffer;
702 use vortex_error::VortexResult;
703 use vortex_utils::aliases::hash_map::HashMap;
704
705 use super::ErasedStats;
706 use super::IntegerStats;
707 use super::typed_int_stats;
708
709 #[test]
710 fn test_naive_count_distinct_values() -> VortexResult<()> {
711 let mut ctx = array_session().create_execution_ctx();
712 let array = PrimitiveArray::new(buffer![217u8, 0], Validity::NonNullable);
713 let stats = typed_int_stats::<u8>(&array, true, &mut ctx)?;
714 assert_eq!(stats.distinct_count().unwrap(), 2);
715 Ok(())
716 }
717
718 #[test]
719 fn test_naive_count_distinct_values_nullable() -> VortexResult<()> {
720 let mut ctx = array_session().create_execution_ctx();
721 let array = PrimitiveArray::new(
722 buffer![217u8, 0],
723 Validity::from(BitBuffer::from(vec![true, false])),
724 );
725 let stats = typed_int_stats::<u8>(&array, true, &mut ctx)?;
726 assert_eq!(stats.distinct_count().unwrap(), 1);
727 Ok(())
728 }
729
730 #[test]
731 fn test_count_distinct_values() -> VortexResult<()> {
732 let mut ctx = array_session().create_execution_ctx();
733 let array = PrimitiveArray::new((0..128u8).collect::<Buffer<u8>>(), Validity::NonNullable);
734 let stats = typed_int_stats::<u8>(&array, true, &mut ctx)?;
735 assert_eq!(stats.distinct_count().unwrap(), 128);
736 Ok(())
737 }
738
739 #[test]
740 fn test_count_distinct_values_nullable() -> VortexResult<()> {
741 let mut ctx = array_session().create_execution_ctx();
742 let array = PrimitiveArray::new(
743 (0..128u8).collect::<Buffer<u8>>(),
744 Validity::from(BitBuffer::from_iter(
745 iter::repeat_n(vec![true, false], 64).flatten(),
746 )),
747 );
748 let stats = typed_int_stats::<u8>(&array, true, &mut ctx)?;
749 assert_eq!(stats.distinct_count().unwrap(), 64);
750 Ok(())
751 }
752
753 #[test]
754 fn dense_distinct_signed_full_range() -> VortexResult<()> {
755 let mut ctx = array_session().create_execution_ctx();
756 let values: Buffer<i8> = (i8::MIN..=i8::MAX)
757 .chain(i8::MIN..=i8::MAX)
758 .chain(iter::once(i8::MAX))
759 .collect();
760 let array = PrimitiveArray::new(values, Validity::NonNullable);
761
762 let stats = typed_int_stats::<i8>(&array, true, &mut ctx)?;
763 assert_eq!(stats.distinct_count(), Some(256));
764 assert_eq!(
765 stats.most_frequent_value_and_count(),
766 Some((i8::MAX.into(), 3))
767 );
768 Ok(())
769 }
770
771 #[test]
772 fn dense_distinct_ignores_values_under_nulls() -> VortexResult<()> {
773 let mut ctx = array_session().create_execution_ctx();
774 let array = PrimitiveArray::new(
775 buffer![1000i32, 1001, 1000, 5_000_000, 1003],
776 Validity::from_iter([true, true, true, false, true]),
777 );
778
779 let stats = typed_int_stats::<i32>(&array, true, &mut ctx)?;
780 assert_eq!(stats.distinct_count(), Some(3));
781 assert_eq!(
782 stats.most_frequent_value_and_count(),
783 Some((1000i32.into(), 2))
784 );
785 Ok(())
786 }
787
788 #[test]
789 fn wide_range_distinct() -> VortexResult<()> {
790 let mut ctx = array_session().create_execution_ctx();
791 let array = PrimitiveArray::new(buffer![0u64, u64::MAX, 0], Validity::NonNullable);
792
793 let stats = typed_int_stats::<u64>(&array, true, &mut ctx)?;
794 assert_eq!(stats.distinct_count(), Some(2));
795 assert_eq!(
796 stats.most_frequent_value_and_count(),
797 Some((0u64.into(), 2))
798 );
799 Ok(())
800 }
801
802 #[test]
803 fn test_integer_stats_leading_nulls() {
804 let mut ctx = array_session().create_execution_ctx();
805 let ints = PrimitiveArray::new(buffer![0, 1, 2], Validity::from_iter([false, true, true]));
806
807 let stats = IntegerStats::generate_opts(
808 &ints,
809 crate::stats::GenerateStatsOptions {
810 count_distinct_values: true,
811 },
812 &mut ctx,
813 );
814
815 assert_eq!(stats.value_count, 2);
816 assert_eq!(stats.null_count, 1);
817 assert_eq!(stats.average_run_length, 1);
818 assert_eq!(stats.distinct_count().unwrap(), 2);
819 }
820
821 fn naive_stats(values: &[u32], valid: &[bool]) -> (HashMap<u32, u32>, u32) {
823 let mut counts = HashMap::default();
824 let mut runs = 0;
825 let mut prev = None;
826 for (&value, _) in values.iter().zip(valid).filter(|(_, ok)| **ok) {
827 *counts.entry(value).or_insert(0) += 1;
828 if prev.replace(value) != Some(value) {
829 runs += 1;
830 }
831 }
832 (counts, runs)
833 }
834
835 #[rstest]
839 fn test_matches_naive_reference(
840 #[values(1, 63, 64, 65, 64 * 20 + 17)] len: u32,
841 #[values(1, 3, 64, 100)] run_len: u32,
842 #[values(None, Some(97), Some(3))] null_every: Option<u32>,
843 #[values(false, true)] null_chunks: bool,
844 #[values(false, true)] count_distinct_values: bool,
845 ) -> VortexResult<()> {
846 let values: Vec<u32> = (0..len).map(|i| (i / run_len) % 16).collect();
847 let valid: Vec<bool> = (0..len)
848 .map(|i| {
849 null_every.is_none_or(|n| i % n != 0 && !(null_chunks && (64..192).contains(&i)))
850 })
851 .collect();
852 let (expected, runs) = naive_stats(&values, &valid);
853
854 let validity = match null_every {
855 None => Validity::NonNullable,
856 Some(_) => Validity::from(BitBuffer::from(valid)),
857 };
858 let array = PrimitiveArray::new(Buffer::from(values), validity);
859 let mut ctx = array_session().create_execution_ctx();
860 let stats = typed_int_stats::<u32>(&array, count_distinct_values, &mut ctx)?;
861
862 let ErasedStats::U32(typed) = stats.erased() else {
863 unreachable!()
864 };
865 if count_distinct_values {
866 let actual: HashMap<u32, u32> = typed
867 .distinct()
868 .map(|d| d.distinct_values().iter().map(|(k, &c)| (k.0, c)).collect())
869 .unwrap_or_default();
870 assert_eq!(actual, expected);
871 }
872 let value_count: u32 = expected.values().sum();
873 assert_eq!(stats.value_count, value_count);
874 if value_count > 0 {
875 assert_eq!(stats.average_run_length, value_count / runs);
876 }
877 Ok(())
878 }
879}