use std::str::FromStr; use crate::{Problem, Solution}; pub struct Day02; impl Problem for Day02 { const DAY: u8 = 2; const INPUT: &'static str = include_str!("../input/day_02.txt"); } impl Solution for Day02 { type Answer1 = usize; type Answer2 = usize; fn part_1(input: &str) -> anyhow::Result { // only 12 red cubes, 13 green cubes, and 14 blue cubes const MAX_SET: CubeSet = CubeSet { red: 12, green: 13, blue: 14, }; Ok(input .trim() .split('\n') .map(Game::from_str) .try_collect::>()? .iter() .filter_map(|s| s.is_valid(&MAX_SET).then_some(s.id)) .sum()) } fn part_2(input: &str) -> anyhow::Result { Ok(input .trim() .split('\n') .map(Game::from_str) .try_collect::>()? .into_iter() .map(Game::min_set) .try_collect::>()? .iter() .map(CubeSet::power) .sum()) } } #[derive(Debug, Default)] struct Game { id: usize, sets: Vec, } impl Game { fn is_valid(&self, max_set: &CubeSet) -> bool { self.sets.iter().all(|c| c.is_valid(max_set)) } fn min_set(self) -> Result { self.sets .into_iter() .reduce(|acc, c| c.maximize(acc)) .ok_or(anyhow::format_err!("No sets found in game")) } } impl FromStr for Game { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let (id, round) = s .trim() .split_once(':') .ok_or(anyhow::format_err!("Invalid game format"))?; let id = id .strip_prefix("Game ") .ok_or(anyhow::format_err!("Invalid game format"))? .parse()?; let round = round .trim() .split(';') .map(CubeSet::from_str) .collect::>()?; Ok(Game { id, sets: round }) } } #[derive(Debug, Default)] struct CubeSet { red: u16, green: u16, blue: u16, } impl CubeSet { fn is_valid(&self, CubeSet { red, green, blue }: &CubeSet) -> bool { &self.red <= red && &self.green <= green && &self.blue <= blue } fn maximize(&self, CubeSet { red, green, blue }: CubeSet) -> CubeSet { CubeSet { red: self.red.max(red), green: self.green.max(green), blue: self.blue.max(blue), } } fn power(&self) -> usize { (self.red * self.green * self.blue) as usize } } impl FromStr for CubeSet { type Err = anyhow::Error; fn from_str(s: &str) -> Result { s.trim().split(',').try_fold(Self::default(), |mut acc, s| { let (n, c) = s .trim() .split_once(' ') .ok_or(anyhow::format_err!("Invalid cube format: {s}"))?; let n = u16::from_str(n)?; match c { "red" => acc.red += n, "green" => acc.green += n, "blue" => acc.blue += n, _ => anyhow::bail!("Invalid cube color"), }; Ok(acc) }) } } #[cfg(test)] mod tests { use super::*; const INPUT: &str = indoc::indoc! {" Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green "}; #[test] fn test_part_1() -> anyhow::Result<()> { Ok(assert_eq!(8, Day02::part_1(INPUT)?)) } #[test] fn test_part_2() -> anyhow::Result<()> { Ok(assert_eq!(2286, Day02::part_2(INPUT)?)) } }