use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Stat<T> {
Known {
value: T,
class: Class,
},
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Class {
Exact,
Certified {
bound: f64,
},
Estimated {
source: Source,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Source {
Sketch,
Quantile,
Sample,
Zone,
Dictionary,
Synopsis,
Propagation,
Observation,
Constant,
}
impl Source {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Sketch => "sketch",
Self::Quantile => "quantile",
Self::Sample => "sample",
Self::Zone => "zone",
Self::Dictionary => "dictionary",
Self::Synopsis => "synopsis",
Self::Propagation => "propagation",
Self::Observation => "observation",
Self::Constant => "constant",
}
}
}
impl fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl Class {
#[must_use]
pub const fn is_exact(self) -> bool {
matches!(self, Self::Exact)
}
#[must_use]
pub fn combine(self, other: Self) -> Self {
match (self, other) {
(Self::Exact, Self::Exact) => Self::Exact,
(Self::Exact, class) | (class, Self::Exact) => class,
(Self::Certified { bound: left }, Self::Certified { bound: right }) => {
Self::Certified { bound: (left + right).min(1.0) }
}
(Self::Estimated { source: Source::Constant }, _)
| (_, Self::Estimated { source: Source::Constant }) => {
Self::Estimated { source: Source::Constant }
}
_ => Self::Estimated { source: Source::Propagation },
}
}
}
impl fmt::Display for Class {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exact => f.write_str("exact"),
Self::Certified { bound } => write!(f, "certified to {:.2}%", bound * 100.0),
Self::Estimated { source } => write!(f, "estimated from {source}"),
}
}
}
impl<T> Stat<T> {
pub const fn exact(value: T) -> Self {
Self::Known { value, class: Class::Exact }
}
pub const fn certified(value: T, bound: f64) -> Self {
Self::Known { value, class: Class::Certified { bound } }
}
pub const fn estimated(value: T, source: Source) -> Self {
Self::Known { value, class: Class::Estimated { source } }
}
#[must_use]
pub const fn is_known(&self) -> bool {
matches!(self, Self::Known { .. })
}
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown)
}
#[must_use]
pub const fn value(&self) -> Option<&T> {
match self {
Self::Known { value, .. } => Some(value),
Self::Unknown => None,
}
}
#[must_use]
pub const fn exact_value(&self) -> Option<&T> {
match self {
Self::Known { value, class: Class::Exact } => Some(value),
_ => None,
}
}
#[must_use]
pub const fn class(&self) -> Option<Class> {
match self {
Self::Known { class, .. } => Some(*class),
Self::Unknown => None,
}
}
#[must_use]
pub fn unwrap_or(self, default: T) -> T {
match self {
Self::Known { value, .. } => value,
Self::Unknown => default,
}
}
#[must_use]
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Stat<U> {
match self {
Self::Known { value, class } => Stat::Known { value: f(value), class },
Self::Unknown => Stat::Unknown,
}
}
#[must_use]
pub fn zip<U, V>(self, other: Stat<U>, f: impl FnOnce(T, U) -> V) -> Stat<V> {
match (self, other) {
(
Self::Known { value: left, class: first },
Stat::Known { value: right, class: second },
) => Stat::Known { value: f(left, right), class: first.combine(second) },
_ => Stat::Unknown,
}
}
}
impl<T> Default for Stat<T> {
fn default() -> Self {
Self::Unknown
}
}
impl<T: fmt::Display> fmt::Display for Stat<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Known { value, class } => write!(f, "{value} ({class})"),
Self::Unknown => f.write_str("unknown"),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Classes {
exact: u64,
certified: u64,
estimated: u64,
unknown: u64,
}
impl Classes {
#[must_use]
pub const fn new() -> Self {
Self { exact: 0, certified: 0, estimated: 0, unknown: 0 }
}
pub fn record<T>(&mut self, stat: &Stat<T>) {
self.record_class(stat.class());
}
pub fn record_class(&mut self, class: Option<Class>) {
match class {
Some(Class::Exact) => self.exact += 1,
Some(Class::Certified { .. }) => self.certified += 1,
Some(Class::Estimated { .. }) => self.estimated += 1,
None => self.unknown += 1,
}
}
#[must_use]
pub const fn exact(self) -> u64 {
self.exact
}
#[must_use]
pub const fn certified(self) -> u64 {
self.certified
}
#[must_use]
pub const fn estimated(self) -> u64 {
self.estimated
}
#[must_use]
pub const fn unknown(self) -> u64 {
self.unknown
}
#[must_use]
pub const fn total(self) -> u64 {
self.exact + self.certified + self.estimated + self.unknown
}
#[must_use]
pub fn known_share(self) -> f64 {
let total = self.total();
if total == 0 {
return 0.0;
}
#[expect(clippy::cast_precision_loss, reason = "a share is a report and not an answer")]
{
(total - self.unknown) as f64 / total as f64
}
}
pub fn merge(&mut self, other: Self) {
self.exact += other.exact;
self.certified += other.certified;
self.estimated += other.estimated;
self.unknown += other.unknown;
}
}
impl fmt::Display for Classes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"exact {}, certified {}, estimated {}, unknown {}",
self.exact, self.certified, self.estimated, self.unknown
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unknown_is_the_default() {
let stat: Stat<u64> = Stat::default();
assert!(stat.is_unknown());
assert_eq!(stat.value(), None);
assert_eq!(stat.class(), None);
assert_eq!(stat.unwrap_or(7), 7);
}
#[test]
fn only_an_exact_answer_comes_back_from_exact_value() {
assert_eq!(Stat::exact(4_u64).exact_value(), Some(&4));
assert_eq!(Stat::certified(4_u64, 0.01).exact_value(), None);
assert_eq!(Stat::estimated(4_u64, Source::Sketch).exact_value(), None);
assert_eq!(Stat::<u64>::Unknown.exact_value(), None);
}
#[test]
fn a_class_degrades_when_it_is_combined() {
assert_eq!(Class::Exact.combine(Class::Exact), Class::Exact);
assert_eq!(
Class::Exact.combine(Class::Estimated { source: Source::Sample }),
Class::Estimated { source: Source::Sample }
);
assert_eq!(
Class::Certified { bound: 0.01 }.combine(Class::Certified { bound: 0.02 }),
Class::Certified { bound: 0.03 }
);
assert_eq!(
Class::Estimated { source: Source::Sketch }
.combine(Class::Estimated { source: Source::Sample }),
Class::Estimated { source: Source::Propagation }
);
assert_eq!(
Class::Estimated { source: Source::Sketch }
.combine(Class::Estimated { source: Source::Constant }),
Class::Estimated { source: Source::Constant }
);
}
#[test]
fn a_certified_bound_saturates_rather_than_growing_past_everything() {
assert_eq!(
Class::Certified { bound: 0.8 }.combine(Class::Certified { bound: 0.7 }),
Class::Certified { bound: 1.0 }
);
}
#[test]
fn zip_is_unknown_when_either_side_is() {
let known = Stat::exact(10_u64);
let unknown = Stat::<u64>::Unknown;
assert_eq!(known.zip(unknown, |left, right| left + right), Stat::Unknown);
assert_eq!(unknown.zip(known, |left, right| left + right), Stat::Unknown);
assert_eq!(known.zip(Stat::exact(5), |left, right| left + right), Stat::exact(15));
}
#[test]
fn map_carries_the_class() {
let bytes = Stat::certified(100_u64, 0.05).map(|rows| rows * 8);
assert_eq!(bytes, Stat::certified(800, 0.05));
}
#[test]
fn the_histogram_counts_what_it_was_shown() {
let mut classes = Classes::new();
classes.record(&Stat::exact(1_u64));
classes.record(&Stat::certified(1_u64, 0.1));
classes.record(&Stat::estimated(1_u64, Source::Zone));
classes.record(&Stat::<u64>::Unknown);
assert_eq!(classes.total(), 4);
assert_eq!(classes.exact(), 1);
assert_eq!(classes.known_share(), 0.75);
assert_eq!(classes.to_string(), "exact 1, certified 1, estimated 1, unknown 1");
let mut all = Classes::new();
all.merge(classes);
all.merge(classes);
assert_eq!(all.total(), 8);
}
#[test]
fn an_empty_histogram_knows_nothing_rather_than_everything() {
assert_eq!(Classes::new().known_share(), 0.0);
assert_eq!(Classes::new().total(), 0);
}
#[test]
fn an_answer_prints_its_provenance() {
assert_eq!(Stat::exact(12_u64).to_string(), "12 (exact)");
assert_eq!(Stat::certified(12_u64, 0.025).to_string(), "12 (certified to 2.50%)");
assert_eq!(
Stat::estimated(12_u64, Source::Sketch).to_string(),
"12 (estimated from sketch)"
);
assert_eq!(Stat::<u64>::Unknown.to_string(), "unknown");
}
}