diff options
Diffstat (limited to 'src/day_02.rs')
-rw-r--r-- | src/day_02.rs | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/src/day_02.rs b/src/day_02.rs new file mode 100644 index 0000000..30607b6 --- /dev/null +++ b/src/day_02.rs @@ -0,0 +1,163 @@ +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<Self::Answer1> { + // 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::<Vec<_>>()? + .iter() + .filter_map(|s| s.is_valid(&MAX_SET).then_some(s.id)) + .sum()) + } + + fn part_2(input: &str) -> anyhow::Result<Self::Answer2> { + Ok(input + .trim() + .split('\n') + .map(Game::from_str) + .try_collect::<Vec<_>>()? + .into_iter() + .map(Game::min_set) + .try_collect::<Vec<_>>()? + .iter() + .map(CubeSet::power) + .sum()) + } +} + +#[derive(Debug, Default)] +struct Game { + id: usize, + sets: Vec<CubeSet>, +} + +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<CubeSet, anyhow::Error> { + 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<Self, Self::Err> { + 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::<Result<_, _>>()?; + + 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<Self, Self::Err> { + 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)?)) + } +} |