use core::marker::PhantomData;
use super::strategy::{BinIndex, BinningStrategy};
#[derive(Debug, Clone)]
pub struct Histogram<S, V> {
strategy: S,
bins: Vec<u64>,
pub nan_count: u64,
pub underflow_count: u64,
pub overflow_count: u64,
pub total_count: u64,
_value: PhantomData<fn() -> V>,
}
impl<S, V> Histogram<S, V> {
#[allow(dead_code)] pub(crate) fn new(
strategy: S,
bins: Vec<u64>,
nan_count: u64,
underflow_count: u64,
overflow_count: u64,
) -> Self {
let total_count =
bins.iter().copied().sum::<u64>() + nan_count + underflow_count + overflow_count;
Self {
strategy,
bins,
nan_count,
underflow_count,
overflow_count,
total_count,
_value: PhantomData,
}
}
}
impl<S, V> Histogram<S, V> {
#[inline]
pub fn bins(&self) -> &[u64] {
&self.bins
}
#[inline]
pub fn count_at_bin(&self, index: usize) -> u64 {
assert!(
index < self.bins.len(),
"Histogram::count_at_bin: index {} out of range (bin_count = {})",
index,
self.bins.len()
);
self.bins[index]
}
#[inline]
pub fn strategy(&self) -> &S {
&self.strategy
}
pub fn cumulative(&self) -> Vec<u64> {
let mut out = self.bins.clone();
for i in 1..out.len() {
out[i] += out[i - 1];
}
out
}
}
impl<S, V> Histogram<S, V>
where
V: Copy,
S: BinningStrategy<V>,
{
#[inline]
pub fn count_for(&self, value: V) -> Option<u64> {
match self.strategy.bin_index(value) {
BinIndex::In(i) => Some(self.bins[i]),
BinIndex::Underflow | BinIndex::Overflow | BinIndex::Nan => None,
}
}
#[inline]
pub fn bin_range(&self, index: usize) -> (S::Range, S::Range) {
self.strategy.bin_range(index)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analyze::histogram::strategy::{CustomBins, LinearBins, NaturalBins};
use std::num::Saturating;
#[test]
fn new_computes_total_count_from_counters() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![1, 2, 3, 4], 5, 6, 7);
assert_eq!(h.total_count, 1 + 2 + 3 + 4 + 5 + 6 + 7);
}
#[test]
fn new_stores_supplied_counters_verbatim() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![10, 20], 1, 2, 3);
assert_eq!(h.nan_count, 1);
assert_eq!(h.underflow_count, 2);
assert_eq!(h.overflow_count, 3);
}
#[test]
fn new_with_empty_bins_has_only_outlier_total() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![], 4, 5, 6);
assert_eq!(h.total_count, 15);
assert!(h.bins().is_empty());
}
#[test]
fn bins_returns_supplied_slice() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![7, 8, 9], 0, 0, 0);
assert_eq!(h.bins(), &[7, 8, 9]);
}
#[test]
fn count_at_bin_returns_value_at_index() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![7, 8, 9], 0, 0, 0);
assert_eq!(h.count_at_bin(0), 7);
assert_eq!(h.count_at_bin(1), 8);
assert_eq!(h.count_at_bin(2), 9);
}
#[test]
#[should_panic(expected = "out of range")]
fn count_at_bin_panics_when_out_of_range() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![7, 8, 9], 0, 0, 0);
let _ = h.count_at_bin(3);
}
#[test]
fn strategy_returns_supplied_strategy() {
let s = LinearBins {
min: 0.0,
max: 1.0,
bin_count: 4,
};
let h: Histogram<LinearBins, f32> = Histogram::new(s, vec![0; 4], 0, 0, 0);
assert_eq!(
*h.strategy(),
LinearBins {
min: 0.0,
max: 1.0,
bin_count: 4
}
);
}
#[test]
fn cumulative_on_empty_bins_is_empty() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![], 0, 0, 0);
assert!(h.cumulative().is_empty());
}
#[test]
fn cumulative_on_single_bin_is_identity() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![42], 0, 0, 0);
assert_eq!(h.cumulative(), vec![42]);
}
#[test]
fn cumulative_on_multiple_bins_is_running_sum() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![1, 2, 3, 4], 0, 0, 0);
assert_eq!(h.cumulative(), vec![1, 3, 6, 10]);
}
#[test]
fn cumulative_excludes_outlier_counters() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![1, 2, 3], 99, 99, 99);
assert_eq!(h.cumulative(), vec![1, 3, 6]);
}
#[test]
fn count_for_natural_u8_returns_some_for_in_range() {
let mut bins = vec![0u64; 256];
bins[42] = 7;
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, bins, 0, 0, 0);
assert_eq!(h.count_for(42_u8), Some(7));
assert_eq!(h.count_for(0_u8), Some(0));
}
#[test]
fn count_for_natural_satu8_dispatches_through_wrapper_impl() {
let mut bins = vec![0u64; 256];
bins[200] = 11;
let h: Histogram<NaturalBins, Saturating<u8>> = Histogram::new(NaturalBins, bins, 0, 0, 0);
assert_eq!(h.count_for(Saturating(200_u8)), Some(11));
}
#[test]
fn count_for_returns_none_for_underflow_overflow_nan() {
let s = LinearBins {
min: 0.0,
max: 1.0,
bin_count: 4,
};
let h: Histogram<LinearBins, f32> = Histogram::new(s, vec![1, 1, 1, 1], 0, 0, 0);
assert_eq!(h.count_for(-0.5_f32), None); assert_eq!(h.count_for(1.5_f32), None); assert_eq!(h.count_for(f32::NAN), None); }
#[test]
fn bin_range_delegates_to_strategy_natural() {
let h: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![0; 256], 0, 0, 0);
assert_eq!(h.bin_range(17), (17_u8, 17_u8));
}
#[test]
fn bin_range_delegates_to_strategy_linear() {
let s = LinearBins {
min: 0.0,
max: 1.0,
bin_count: 4,
};
let h: Histogram<LinearBins, f32> = Histogram::new(s, vec![0; 4], 0, 0, 0);
let (lo, hi) = h.bin_range(0);
assert_eq!(lo, 0.0);
assert_eq!(hi, 0.25);
let (_, hi_last) = h.bin_range(3);
assert_eq!(hi_last, 1.0);
}
#[test]
fn bin_range_delegates_to_strategy_custom() {
let s = CustomBins {
edges: vec![0.0, 0.5, 1.0, 4.0],
};
let h: Histogram<CustomBins, f64> = Histogram::new(s, vec![0; 3], 0, 0, 0);
assert_eq!(h.bin_range(0), (0.0, 0.5));
assert_eq!(h.bin_range(2), (1.0, 4.0));
}
#[test]
#[should_panic(expected = "out of range")]
fn bin_range_panics_when_strategy_panics() {
let s = LinearBins {
min: 0.0,
max: 1.0,
bin_count: 4,
};
let h: Histogram<LinearBins, f32> = Histogram::new(s, vec![0; 4], 0, 0, 0);
let _ = h.bin_range(4);
}
#[test]
fn v_phantom_distinguishes_u8_and_satu8_histograms() {
let h_idx: Histogram<NaturalBins, u8> = Histogram::new(NaturalBins, vec![0; 256], 0, 0, 0);
let h_mono: Histogram<NaturalBins, Saturating<u8>> =
Histogram::new(NaturalBins, vec![0; 256], 0, 0, 0);
assert_eq!(h_idx.count_for(0_u8), Some(0));
assert_eq!(h_mono.count_for(Saturating(0_u8)), Some(0));
}
}