diff --git a/gamelog/src/file.rs b/gamelog/src/file.rs index 4c9dab1..9574fb9 100644 --- a/gamelog/src/file.rs +++ b/gamelog/src/file.rs @@ -1,68 +1,56 @@ -use crate::{Action, Play, Team, error}; +use crate::{Action, Game, Play, Team, error}; use serde::Deserialize; -use std::{fs::File, path::PathBuf}; +use std::{fs::File, path::PathBuf, usize}; use strum::IntoEnumIterator; -#[derive(Debug, Deserialize, Clone)] -pub struct LogFile(pub Vec); +#[derive(Debug, Deserialize, Clone, Default)] +pub struct LogFile(pub Vec); impl LogFile { /// Returns the most common action for a given team. pub fn most_frequent_action(&self, team: Team) -> Action { - let mut most_freq_action = Action::Unknown; - let mut frequency = 0; + let mut most_freq_action = Action::default(); + let mut frequency = usize::MIN; + let mut found = usize::MIN; + let team_actions = self.team_actions(team).into_iter(); - // The following let statement is equivalent to: - // - // let team_actions = { - // let mut actions = vec![]; - // - // for game in &self.0 { - // for play in game.team_plays(team.to_owned()).0 { - // actions.push(play.action.to_owned()) - // } - // } - // - // actions - // } - // .into_iter(); - // - // I just write iterators more naturally for some reason - // despite them being less readable afterward. I came from - // loving python's list comprehensions. - // I suppose I like the lack of nesting. - // - // I have no clue if the iterator is actually more efficient. + Action::iter() + .filter(|action| *action != Action::Unknown) + .for_each(|action| { + found = team_actions.clone().filter(|a| *a == action).count(); - let team_actions = self - .0 - .iter() - .filter_map(|game| Some(game.team_plays(team.to_owned()).0)) - .collect::>>() - .concat() - .iter() - .filter_map(|play| Some(play.action.to_owned())) - .collect::>() - .into_iter(); - - let mut found: usize; - - for action in Action::iter() { - if action == Action::Unknown { - continue; - } - - found = team_actions.clone().filter(|a| *a == action).count(); - - if found > frequency { - frequency = found; - most_freq_action = action.to_owned(); - } - } + if found > frequency { + frequency = found; + most_freq_action = action.to_owned(); + } + }); most_freq_action } + /// Returns the least common action for a given team. + /// This action has to have been played at least once. + pub fn least_frequent_action(&self, team: Team) -> Action { + let mut least_freq_action = Action::default(); + let mut frequency = usize::MAX; + let mut found = usize::MAX; + let team_actions = self.team_actions(team).into_iter(); + + Action::iter() + .filter(|action| *action != Action::Unknown) + .for_each(|action| { + found = team_actions.clone().filter(|a| *a == action).count(); + + if (found != 0_usize) && (found < frequency) { + dbg!("hit"); + frequency = found; + least_freq_action = action.to_owned(); + } + }); + + least_freq_action + } + pub fn check_teams(self) -> Result { for game in &self.0 { if let Err(err) = game.teams() { @@ -72,6 +60,38 @@ impl LogFile { Ok(self) } + + /// Returns the team actions. + /// The following code is equivalent to: + fn team_actions(&self, team: Team) -> Vec { + // ``` + // fn foo(&self, team: Team) -> Vec { + // let mut actions = vec![]; + // + // for game in &self.0 { + // for play in game.team_plays(team.to_owned()).0 { + // actions.push(play.action.to_owned()) + // } + // } + // + // actions + // } + // ``` + // I just write iterators more naturally for some reason + // despite them being less readable afterward. I came from + // loving python's list comprehensions. + // I suppose I like the lack of nesting. + // + // I have no clue if the iterator is actually more efficient. + self.0 + .iter() + .filter_map(|game| Some(game.team_plays(team.to_owned()).0)) + .collect::>>() + .concat() + .iter() + .filter_map(|play| Some(play.action.to_owned())) + .collect::>() + } } impl TryFrom for LogFile { @@ -97,3 +117,84 @@ impl TryFrom for LogFile { ) } } + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn most_frequent_action() { + let a = LogFile(vec![Game { + version: crate::MIN_VER, + flags: vec![], + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Curls, + ..Default::default() + }), + Event::Play(Play { + action: Action::Curls, + ..Default::default() + }), + Event::Play(Play { + action: Action::SlotOut, + ..Default::default() + }), + Event::Kickoff(Team::ArizonaState), + ], + }]); + + assert!(a.most_frequent_action(Team::Nebraska) == Action::Mesh) + } + + #[test] + fn least_frequent_action() { + let a = LogFile(vec![Game { + version: crate::MIN_VER, + flags: vec![], + events: vec![ + Event::Kickoff(Team::Nebraska), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Mesh, + ..Default::default() + }), + Event::Play(Play { + action: Action::Curls, + ..Default::default() + }), + Event::Play(Play { + action: Action::Curls, + ..Default::default() + }), + Event::Play(Play { + action: Action::SlotOut, + ..Default::default() + }), + Event::Kickoff(Team::ArizonaState), + ], + }]); + + assert!(a.least_frequent_action(Team::Nebraska) == Action::SlotOut) + } +} diff --git a/gamelog/src/game.rs b/gamelog/src/game.rs index 0cd0274..0b2598b 100644 --- a/gamelog/src/game.rs +++ b/gamelog/src/game.rs @@ -513,7 +513,7 @@ mod tests { Event::Kickoff(Team::Nebraska), Event::Play(Play::default()), Event::Score(ScorePoints::default()), - Event::Kickoff(Team::SouthCarolina), + Event::Kickoff(Team::ArizonaState), Event::Play(Play::default()), Event::Turnover(Team::Nebraska), Event::Play(Play::default()),