#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct HistBin {
pub low: f32,
pub high: f32,
pub count: usize,
}
pub struct HistogramBuilder {
min: f32,
max: f32,
bins: Vec<HistBin>,
}
pub fn new_histogram(min: f32, max: f32, num_bins: usize) -> HistogramBuilder {
let num_bins = num_bins.max(1);
let width = (max - min) / num_bins as f32;
let bins = (0..num_bins)
.map(|i| HistBin {
low: min + i as f32 * width,
high: min + (i + 1) as f32 * width,
count: 0,
})
.collect();
HistogramBuilder { min, max, bins }
}
impl HistogramBuilder {
pub fn add(&mut self, value: f32) {
if self.bins.is_empty() {
return;
}
if !(self.min..=self.max).contains(&value) {
return;
}
let n = self.bins.len();
let idx = if (value - self.max).abs() < 1e-9 {
n - 1
} else {
let width = (self.max - self.min) / n as f32;
((value - self.min) / width) as usize
};
if idx < n {
self.bins[idx].count += 1;
}
}
pub fn add_many(&mut self, values: &[f32]) {
for &v in values {
self.add(v);
}
}
pub fn counts(&self) -> Vec<usize> {
self.bins.iter().map(|b| b.count).collect()
}
pub fn normalized(&self) -> Vec<f32> {
let total: usize = self.bins.iter().map(|b| b.count).sum();
if total == 0 {
return vec![0.0; self.bins.len()];
}
self.bins
.iter()
.map(|b| b.count as f32 / total as f32)
.collect()
}
pub fn total(&self) -> usize {
self.bins.iter().map(|b| b.count).sum()
}
pub fn bin_count(&self) -> usize {
self.bins.len()
}
pub fn bin_of(&self, value: f32) -> Option<&HistBin> {
self.bins.iter().find(|b| (b.low..b.high).contains(&value))
}
pub fn reset(&mut self) {
for b in &mut self.bins {
b.count = 0;
}
}
pub fn mode_bin(&self) -> Option<&HistBin> {
self.bins.iter().max_by_key(|b| b.count)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_histogram() {
let h = new_histogram(0.0, 10.0, 5);
assert_eq!(h.total(), 0);
}
#[test]
fn test_add_single() {
let mut h = new_histogram(0.0, 10.0, 10);
h.add(5.0);
assert_eq!(h.total(), 1);
}
#[test]
fn test_add_many_counts() {
let mut h = new_histogram(0.0, 4.0, 4);
h.add_many(&[0.5, 1.5, 2.5, 3.5]);
assert_eq!(h.total(), 4);
}
#[test]
fn test_normalized_sums_to_one() {
let mut h = new_histogram(0.0, 10.0, 5);
h.add_many(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]);
let sum: f32 = h.normalized().iter().sum();
assert!((sum - 1.0).abs() < 1e-5, "sum={sum}");
}
#[test]
fn test_out_of_range_ignored() {
let mut h = new_histogram(0.0, 10.0, 5);
h.add(-1.0);
h.add(11.0);
assert_eq!(h.total(), 0);
}
#[test]
fn test_bin_count() {
let h = new_histogram(0.0, 100.0, 20);
assert_eq!(h.bin_count(), 20);
}
#[test]
fn test_reset() {
let mut h = new_histogram(0.0, 10.0, 5);
h.add_many(&[1.0, 2.0, 3.0]);
h.reset();
assert_eq!(h.total(), 0);
}
#[test]
fn test_mode_bin() {
let mut h = new_histogram(0.0, 4.0, 4);
h.add_many(&[0.5, 0.5, 0.5, 1.5, 2.5]);
let mode = h.mode_bin().expect("should succeed");
assert_eq!(mode.count, 3);
}
#[test]
fn test_counts_length() {
let h = new_histogram(0.0, 10.0, 7);
assert_eq!(h.counts().len(), 7);
}
}