use std::fmt::Display;
use comfy_table::{Cell, Color, ColumnConstraint, Row, Table, Width};
use serde::{Deserialize, Serialize};
use crate::strategy::{Attempts, Strategy, Word};
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Perf {
pub(crate) tries: Vec<(Word, Attempts)>,
strategy_name: String,
}
impl Perf {
pub(crate) fn new(strat: &dyn Strategy) -> Self {
Perf {
tries: Vec::new(),
strategy_name: format!("{} v{}", strat, strat.version()),
}
}
pub fn strategy_name(&self) -> &str {
&self.strategy_name
}
pub fn num_tried(&self) -> u32 {
self.tries.len() as u32
}
pub fn num_solved(&self) -> u32 {
self.tries
.iter()
.filter(|(word, attempts)| attempts.solved(word))
.count() as u32
}
pub fn frac_solved(&self) -> f32 {
(self.num_solved() as f32) / (self.num_tried() as f32)
}
pub fn cumulative_guesses(&self) -> u32 {
self.tries.iter().map(|(_, a)| a.inner().len() as u32).sum()
}
pub fn cumulative_guesses_solved(&self) -> u32 {
self.tries
.iter()
.filter(|(word, attempts)| attempts.solved(word))
.map(|(_, a)| a.inner().len() as u32)
.sum()
}
pub fn guesses_per_solution(&self) -> f32 {
(self.cumulative_guesses_solved() as f32) / (self.num_solved() as f32)
}
pub fn num_missed(&self) -> u32 {
self.num_tried() - self.num_solved()
}
pub fn frac_missed(&self) -> f32 {
(self.num_missed() as f32) / (self.num_tried() as f32)
}
pub fn print(&self) {
print!("{}", self);
let mut table = Table::new();
if !table.is_tty() {
table.set_table_width(80);
} else {
table.load_preset(comfy_table::presets::UTF8_FULL);
}
let columns = (table.get_table_width().unwrap() / 9) as usize;
for chunk in self.tries.chunks(columns) {
let mut row = Row::new();
for (word, attempts) in chunk {
let mut cell = Cell::new(format!("{}\n-----\n{}", word, attempts));
if !attempts.solved(word) {
cell = cell.bg(Color::Red).fg(Color::Black);
}
row.add_cell(cell);
}
table.add_row(row);
}
table.set_constraints(vec![
ColumnConstraint::LowerBoundary(Width::Fixed(5));
columns
]);
println!("{}", table);
}
pub fn to_summary(&self) -> PerfSummary {
let mut histogram = [0; 6];
self.tries
.iter()
.filter(|(word, attempts)| attempts.solved(word))
.map(|(_, attempts)| attempts.inner().len())
.for_each(|n| histogram[n - 1] += 1);
assert_eq!(histogram.iter().sum::<u32>(), self.num_solved());
PerfSummary {
strategy_name: &self.strategy_name,
num_tried: self.num_tried(),
num_solved: self.num_solved(),
cumulative_guesses: self.cumulative_guesses(),
histogram,
}
}
}
impl Display for Perf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let perf_summary = self.to_summary();
write!(f, "{}", perf_summary)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct PerfSummary<'a> {
strategy_name: &'a str,
num_tried: u32,
num_solved: u32,
cumulative_guesses: u32,
histogram: [u32; 6],
}
impl<'a> PerfSummary<'a> {
pub fn strategy_name(&self) -> &'a str {
self.strategy_name
}
pub fn num_tried(&self) -> u32 {
self.num_tried
}
pub fn num_solved(&self) -> u32 {
self.num_solved
}
pub fn frac_solved(&self) -> f32 {
(self.num_solved as f32) / (self.num_tried as f32)
}
pub fn cumulative_guesses(&self) -> u32 {
self.cumulative_guesses
}
pub fn cumulative_guesses_solved(&self) -> u32 {
self.histogram
.iter()
.enumerate()
.map(|(i, v)| i as u32 * v)
.sum::<u32>()
}
pub fn guesses_per_solution(&self) -> f32 {
(self.cumulative_guesses_solved() as f32) / (self.num_solved as f32)
}
pub fn num_missed(&self) -> u32 {
self.num_tried - self.num_solved
}
pub fn frac_missed(&self) -> f32 {
(self.num_missed() as f32) / (self.num_tried as f32)
}
}
impl<'a> Display for PerfSummary<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{:-^80}", self.strategy_name)?;
writeln!(
f,
"Guessed {} ({:.2}%) correctly, {} ({:.2}%) incorrectly out of {} words",
self.num_solved(),
self.frac_solved() * 100.,
self.num_missed(),
self.frac_missed() * 100.,
self.num_tried()
)?;
writeln!(
f,
"Correct guesses took {:.2} attempts on average",
self.guesses_per_solution()
)?;
Ok(())
}
}