use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Kind {
Optimized,
Missed,
Note,
}
impl Kind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Optimized => "optimized",
Self::Missed => "missed",
Self::Note => "note",
}
}
#[must_use]
pub fn parse(word: &str) -> Option<Self> {
match word {
"optimized" => Some(Self::Optimized),
"missed" => Some(Self::Missed),
"note" => Some(Self::Note),
_ => None,
}
}
pub const ALL: [Self; 3] = [Self::Optimized, Self::Missed, Self::Note];
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Event {
pub kind: Kind,
pub what: &'static str,
pub count: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Stats {
events: Vec<Event>,
}
impl Stats {
#[must_use]
pub const fn new() -> Self {
Self { events: Vec::new() }
}
pub fn optimized(&mut self, what: &'static str) {
self.record(Kind::Optimized, what, 1);
}
pub fn missed(&mut self, what: &'static str) {
self.record(Kind::Missed, what, 1);
}
pub fn note(&mut self, what: &'static str) {
self.record(Kind::Note, what, 1);
}
pub fn record(&mut self, kind: Kind, what: &'static str, count: u32) {
if count == 0 {
return;
}
match self.events.iter_mut().find(|it| it.kind == kind && it.what == what) {
Some(event) => event.count += count,
None => self.events.push(Event { kind, what, count }),
}
}
pub fn merge(&mut self, other: &Self) {
for event in &other.events {
self.record(event.kind, event.what, event.count);
}
}
#[must_use]
pub fn changed(&self) -> bool {
self.events.iter().any(|event| event.kind == Kind::Optimized)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
#[must_use]
pub fn events(&self) -> &[Event] {
&self.events
}
pub fn of(&self, kind: Kind) -> impl Iterator<Item = &Event> {
self.events.iter().filter(move |event| event.kind == kind)
}
#[must_use]
pub fn count(&self, kind: Kind, what: &str) -> u32 {
self.events
.iter()
.find(|it| it.kind == kind && it.what == what)
.map_or(0, |event| event.count)
}
#[must_use]
pub fn total(&self, kind: Kind) -> u32 {
self.of(kind).map(|event| event.count).sum()
}
}
#[cfg(test)]
mod tests {
use super::{Kind, Stats};
#[test]
fn a_pass_that_recorded_nothing_changed_nothing() {
let stats = Stats::new();
assert!(!stats.changed());
assert!(stats.is_empty());
assert_eq!(stats.total(Kind::Optimized), 0);
}
#[test]
fn only_an_optimized_event_is_a_change() {
let mut stats = Stats::new();
stats.missed("nothing to see");
stats.note("an analysis said something");
assert!(!stats.changed(), "a miss is not a change");
assert!(!stats.is_empty(), "it still said something");
stats.optimized("a rewrite");
assert!(stats.changed());
}
#[test]
fn the_same_event_twice_is_one_event_with_a_count_of_two() {
let mut stats = Stats::new();
stats.optimized("folded");
stats.optimized("folded");
stats.optimized("removed");
assert_eq!(stats.events().len(), 2);
assert_eq!(stats.count(Kind::Optimized, "folded"), 2);
assert_eq!(stats.count(Kind::Optimized, "removed"), 1);
assert_eq!(stats.total(Kind::Optimized), 3);
}
#[test]
fn the_same_words_under_two_kinds_are_two_events() {
let mut stats = Stats::new();
stats.optimized("folded");
stats.missed("folded");
assert_eq!(stats.events().len(), 2);
assert_eq!(stats.count(Kind::Optimized, "folded"), 1);
assert_eq!(stats.count(Kind::Missed, "folded"), 1);
}
#[test]
fn recording_a_count_of_zero_does_not_make_an_event() {
let mut stats = Stats::new();
stats.record(Kind::Optimized, "folded", 0);
assert!(stats.is_empty(), "an event saying a thing happened no times");
assert!(!stats.changed());
}
#[test]
fn events_come_out_in_the_order_they_first_arrived() {
let mut stats = Stats::new();
stats.missed("first");
stats.optimized("second");
stats.missed("first");
let seen: Vec<&str> = stats.events().iter().map(|event| event.what).collect();
assert_eq!(seen, ["first", "second"], "the second `first` moved it");
}
#[test]
fn merging_adds_the_counts_and_keeps_the_left_hand_order() {
let mut left = Stats::new();
left.optimized("folded");
left.missed("out of fuel");
let mut right = Stats::new();
right.optimized("removed");
right.optimized("folded");
left.merge(&right);
let seen: Vec<(&str, u32)> =
left.events().iter().map(|event| (event.what, event.count)).collect();
assert_eq!(seen, [("folded", 2), ("out of fuel", 1), ("removed", 1)]);
}
#[test]
fn merging_an_empty_record_changes_nothing() {
let mut stats = Stats::new();
stats.optimized("folded");
let before = stats.clone();
stats.merge(&Stats::new());
assert_eq!(stats, before);
}
#[test]
fn the_words_are_the_ones_opt_info_uses_and_they_round_trip() {
for kind in Kind::ALL {
assert_eq!(Kind::parse(kind.as_str()), Some(kind));
assert_eq!(kind.to_string(), kind.as_str());
}
assert_eq!(Kind::parse("all"), None, "`all` is every kind and not one of them");
assert_eq!(Kind::parse("Optimized"), None);
}
#[test]
fn one_kind_at_a_time_is_in_the_order_it_was_recorded() {
let mut stats = Stats::new();
stats.missed("a");
stats.optimized("b");
stats.missed("c");
let missed: Vec<&str> = stats.of(Kind::Missed).map(|event| event.what).collect();
assert_eq!(missed, ["a", "c"]);
assert_eq!(stats.total(Kind::Missed), 2);
}
}