use crate::StrError;
use num_traits::Num;
use std::cmp;
use std::fmt::{self, Write};
pub struct Histogram<T>
where
T: Num + Copy,
{
stations: Vec<T>,
counts: Vec<usize>,
bar_char: char, bar_max_len: usize, }
impl<T> Histogram<T>
where
T: Num + Copy + PartialOrd,
{
pub fn new(stations: &[T]) -> Result<Self, StrError> {
if stations.len() < 2 {
return Err("histogram must have at least 2 stations");
}
let nbins = stations.len() - 1;
Ok(Histogram {
stations: Vec::from(stations),
counts: vec![0; nbins],
bar_char: 'π¦',
bar_max_len: 30,
})
}
pub fn count(&mut self, data: &[T]) {
for x in data {
if let Some(i) = self.find_bin(*x) {
self.counts[i] += 1;
}
}
}
pub fn reset(&mut self) {
for i in 0..self.counts.len() {
self.counts[i] = 0;
}
}
pub fn get_counts(&self) -> &Vec<usize> {
&self.counts
}
pub fn set_bar_char(&mut self, bar_char: char) -> &mut Self {
self.bar_char = bar_char;
self
}
pub fn set_bar_max_len(&mut self, bar_max_len: usize) -> &mut Self {
self.bar_max_len = bar_max_len;
self
}
fn find_bin(&self, x: T) -> Option<usize> {
let nstation = self.stations.len();
if x < self.stations[0] {
return None;
}
if x >= self.stations[nstation - 1] {
return None;
}
let mut upper = nstation;
let mut lower = 0;
let mut mid;
while upper - lower > 1 {
mid = (upper + lower) / 2;
if x >= self.stations[mid] {
lower = mid
} else {
upper = mid
}
}
Some(lower)
}
}
impl<T> fmt::Display for Histogram<T>
where
T: Num + Copy + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let nbins = self.counts.len();
let mut c_max = 0; let mut l_c_max = 0; let mut buf = String::new();
for i in 0..nbins {
let c = self.counts[i];
write!(&mut buf, "{}", c).unwrap();
c_max = cmp::max(c_max, c);
l_c_max = cmp::max(l_c_max, buf.chars().count());
buf.clear();
}
if c_max < 1 {
write!(f, "zero data\n").unwrap();
return Ok(());
}
let mut l_s_max = 0; for i in 0..self.stations.len() {
let station = self.stations[i];
match f.precision() {
Some(digits) => write!(&mut buf, "{:.1$}", station, digits).unwrap(),
None => write!(&mut buf, "{}", station).unwrap(),
}
l_s_max = cmp::max(l_s_max, buf.chars().count());
buf.clear();
}
let scale = (self.bar_max_len as f64) / (c_max as f64);
let mut total = 0;
for i in 0..nbins {
let count = self.counts[i];
let (left, right) = (self.stations[i], self.stations[i + 1]);
total += count;
match f.precision() {
Some(digits) => write!(
f,
"[{:>3$.4$},{:>3$.4$}) | {:>5$}",
left, right, count, l_s_max, digits, l_c_max
)
.unwrap(),
None => write!(f, "[{:>3$},{:>3$}) | {:>4$}", left, right, count, l_s_max, l_c_max).unwrap(),
}
let n = scale * (count as f64);
let bar = std::iter::repeat(self.bar_char).take(n as usize).collect::<String>();
write!(f, " {}\n", bar).unwrap();
}
write!(f, "{:>1$}\n", format!("sum = {}", total), 2 * l_s_max + l_c_max + 6).unwrap();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Histogram;
#[test]
fn new_fails_on_wrong_input() {
assert_eq!(
Histogram::<i32>::new(&[]).err(),
Some("histogram must have at least 2 stations")
);
}
#[test]
fn new_works() {
let stations: [i32; 6] = [0, 1, 2, 3, 4, 5];
let hist = Histogram::new(&stations).unwrap();
assert_eq!(hist.stations.len(), 6);
assert_eq!(hist.counts.len(), 5);
}
#[test]
fn find_bin_works() {
let stations: [f64; 6] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let hist = Histogram::new(&stations).unwrap();
let res = hist.find_bin(-3.3);
assert_eq!(res, None);
let res = hist.find_bin(7.0);
assert_eq!(res, None);
for i in 0..stations.len() {
let res = hist.find_bin(stations[i]);
if i < stations.len() - 1 {
assert_eq!(res, Some(i));
} else {
assert_eq!(res, None);
}
}
let res = hist.find_bin(0.5);
assert_eq!(res, Some(0));
let res = hist.find_bin(1.5);
assert_eq!(res, Some(1));
let res = hist.find_bin(2.5);
assert_eq!(res, Some(2));
let res = hist.find_bin(3.99999999999999);
assert_eq!(res, Some(3));
let res = hist.find_bin(4.999999);
assert_eq!(res, Some(4));
}
#[test]
fn count_and_reset_work() {
#[rustfmt::skip]
let data = [
0.0, 0.1, 0.2, 0.3, 0.9, 1.0, 1.0, 1.0, 1.2, 1.3, 1.4, 1.5, 1.99, 2.0, 2.5, 3.0, 3.5, 4.1, 4.5, 4.9, -3.0, -2.0, -1.0, 5.0, 6.0, 7.0, 8.0, ];
let stations: [f64; 6] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let mut hist = Histogram::new(&stations).unwrap();
hist.count(&data);
assert_eq!(hist.get_counts(), &[5, 8, 2, 2, 3]);
hist.reset();
assert_eq!(hist.get_counts(), &[0, 0, 0, 0, 0]);
#[rustfmt::skip]
let data: [i32; 12]= [
0, 0, 0, 0, 1, 2, 2, 2, 5, 5, -1, 10, ];
let stations: [i32; 6] = [0, 1, 2, 3, 4, 5];
let mut hist = Histogram::new(&stations).unwrap();
hist.count(&data);
assert_eq!(hist.counts, &[4, 1, 3, 0, 0]);
hist.reset();
assert_eq!(hist.counts, &[0, 0, 0, 0, 0]);
}
#[test]
fn display_returns_errors() {
let hist = Histogram::new(&[1, 2]).unwrap();
assert_eq!(format!("{:.3}", hist), "zero data\n");
}
#[test]
fn display_works() {
#[rustfmt::skip]
let data = [
0.0, 0.1, 0.2, 0.3, 0.9, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.4, 1.5, 1.99, 2.0, 2.5, 4.1, 4.5, 4.9, ];
let stations: [f64; 11] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let mut hist = Histogram::new(&stations).unwrap();
hist.count(&data);
hist.set_bar_char('πΆ').set_bar_max_len(10);
assert_eq!(
format!("{:.3}", hist),
"[ 0.000, 1.000) | 5 πΆπΆπΆπΆπΆ\n\
[ 1.000, 2.000) | 10 πΆπΆπΆπΆπΆπΆπΆπΆπΆπΆ\n\
[ 2.000, 3.000) | 2 πΆπΆ\n\
[ 3.000, 4.000) | 0 \n\
[ 4.000, 5.000) | 3 πΆπΆπΆ\n\
[ 5.000, 6.000) | 0 \n\
[ 6.000, 7.000) | 0 \n\
[ 7.000, 8.000) | 0 \n\
[ 8.000, 9.000) | 0 \n\
[ 9.000,10.000) | 0 \n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sum = 20\n"
);
#[rustfmt::skip]
let data = [
0.0, 0.1, 0.2, 0.3, 0.9, 1.0, 1.0, 1.0, 1.2, 1.3, 1.4, 1.5, 1.99, 2.0, 2.5, 3.0, 3.5, 4.1, 4.5, 4.9, -3.0, -2.0, -1.0, 50.0, 60.0, 70.0, 80.0, ];
let stations: [f64; 11] = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
let mut hist = Histogram::new(&stations).unwrap();
hist.count(&data);
assert_eq!(hist.counts, &[5, 8, 2, 2, 3, 0, 0, 0, 0, 0]);
assert_eq!(
format!("{}", hist),
"[ 0, 1) | 5 π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦\n\
[ 1, 2) | 8 π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦\n\
[ 2, 3) | 2 π¦π¦π¦π¦π¦π¦π¦\n\
[ 3, 4) | 2 π¦π¦π¦π¦π¦π¦π¦\n\
[ 4, 5) | 3 π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦π¦\n\
[ 5, 6) | 0 \n\
[ 6, 7) | 0 \n\
[ 7, 8) | 0 \n\
[ 8, 9) | 0 \n\
[ 9,10) | 0 \n\
\x20\x20\x20sum = 20\n"
);
}
}