use std::{iter::Sum, ops::Add};
use anyhow::anyhow;
use crate::{
datatypes::{Index, Name, Set, Teams},
reporting::Report,
simulation::{Simulation, SwissSystem},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct AssessReport {
pub mean: f32,
pub ds: f32,
pub success: u64,
pub n: u64,
pub three_zero_picks: Set,
pub advancing_picks: Set,
pub zero_three_picks: Set,
}
impl AssessReport {
pub fn new<
I1: IntoIterator<Item = Index>,
I2: IntoIterator<Item = Index>,
I3: IntoIterator<Item = Index>,
>(
three_zero_picks: I1,
advanced_picks: I2,
zero_three_picks: I3,
) -> Self {
Self {
mean: 0.0,
ds: 0.0,
success: 0,
n: 0,
three_zero_picks: three_zero_picks.into_iter().collect(),
advancing_picks: advanced_picks.into_iter().collect(),
zero_three_picks: zero_three_picks.into_iter().collect(),
}
}
pub fn try_from_args(
teams: &Teams,
three_zero_str: &[Name; 2],
advancing_str: &[Name; 6],
zero_three_str: &[Name; 2],
) -> anyhow::Result<Self> {
let mut three_zero_picks = Vec::new();
let mut advancing_picks = Vec::new();
let mut zero_three_picks = Vec::new();
for s in three_zero_str {
three_zero_picks.push(Index::try_new(
teams
.names
.iter()
.position(|name| name == s)
.ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
as u16,
)?);
}
for s in advancing_str {
advancing_picks.push(Index::try_new(
teams
.names
.iter()
.position(|name| name == s)
.ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
as u16,
)?);
}
for s in zero_three_str {
zero_three_picks.push(Index::try_new(
teams
.names
.iter()
.position(|name| name == s)
.ok_or_else(|| anyhow!("failed to find team \"{s}\" in the input file"))?
as u16,
)?);
}
let mut seen = Set::new();
for pick in three_zero_picks
.iter()
.chain(advancing_picks.iter())
.chain(zero_three_picks.iter())
{
if !seen.insert(*pick) {
anyhow::bail!("duplicate pick: {}", teams.names[pick.to_usize()]);
}
}
Ok(Self::new(
three_zero_picks,
advancing_picks,
zero_three_picks,
))
}
}
impl Add for AssessReport {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
if self.n == 0 {
rhs
} else {
self.success += rhs.success;
let n_self = self.n as f32;
let n_rhs = rhs.n as f32;
let n = n_self + n_rhs;
self.n += rhs.n;
let delta = self.mean - rhs.mean;
self.mean = n_rhs.mul_add(rhs.mean, n_self * self.mean) / n;
self.ds += (delta * delta).mul_add(n_self * n_rhs / n, rhs.ds);
self
}
}
}
impl Sum for AssessReport {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::default(), |acc, report| acc + report)
}
}
impl Report for AssessReport {
fn update(&mut self, ss: &SwissSystem) {
self.n += 1;
let stars = {
self.three_zero_picks
.iter()
.filter(|&pick| ss.wins[pick.to_usize()] == 3 && ss.losses[pick.to_usize()] == 0)
.count()
+ self
.advancing_picks
.iter()
.filter(|&pick| ss.wins[pick.to_usize()] == 3 && ss.losses[pick.to_usize()] > 0)
.count()
+ self
.zero_three_picks
.iter()
.filter(|&pick| {
ss.wins[pick.to_usize()] == 0 && ss.losses[pick.to_usize()] == 3
})
.count()
};
if stars >= 5 {
self.success += 1;
}
let delta1 = stars as f32 - self.mean;
self.mean += delta1 / self.n as f32;
let delta2 = stars as f32 - self.mean;
self.ds = delta1.mul_add(delta2, self.ds);
}
fn format(&self, _sim: &Simulation) -> String {
let mean = self.mean;
let sd = (self.ds / self.n as f32).sqrt();
let success = self.success as f32 / self.n as f32 * 100.0;
format!(
"\nSimulated stars earned: {mean:.3} +/- {sd:.3}\nExpected success (>=5 stars): {success:.1}%"
)
}
}