use std::cmp::{max, min}; use crate::prelude::*; extern crate nom; mod prelude; use nom::{ branch::alt, bytes::complete::{tag, take_while_m_n}, combinator::{map_res, value}, multi::separated_list0, IResult, }; fn main() { Day2::show(day_input(2), day_input(2)) } type Solution = usize; #[derive(Debug, PartialEq, Clone)] enum Reveal { Red(usize), Green(usize), Blue(usize), } #[derive(Debug, PartialEq)] struct RevealGroup { red: Option, green: Option, blue: Option, } impl RevealGroup { fn power(&self) -> usize { println!("reveal group power for {:?}", self); self.red.unwrap_or(0) * self.green.unwrap_or(0) * self.blue.unwrap_or(0) } } impl From> for RevealGroup { fn from(value: Vec) -> Self { // TODO: this obviously won't work if the vector contains multiple reveals of the same kind // there's probably a simpler way to parse it out let mut result = Self { red: None, blue: None, green: None, }; for v in value { match v { Reveal::Red(v) => result.red = Some(v), Reveal::Green(v) => result.green = Some(v), Reveal::Blue(v) => result.blue = Some(v), } } result } } #[derive(Debug, PartialEq)] struct Game { id: usize, reveal_groups: Vec, } impl Game { fn made_possible(&self) -> RevealGroup { let mut red = 0; let mut green = 0; let mut blue = 0; for rg in &self.reveal_groups { red = max(rg.red.unwrap_or(0), red); green = max(rg.green.unwrap_or(0), green); blue = max(rg.blue.unwrap_or(0), blue); } RevealGroup { red: Some(red), green: Some(green), blue: Some(blue), } } } fn int(input: &str) -> IResult<&str, usize> { map_res(take_while_m_n(1, 16, |c: char| c.is_numeric()), |s| { usize::from_str_radix(s, 10) })(input) } fn reveal(input: &str) -> IResult<&str, Reveal> { let (input, num) = int(input)?; alt(( value(Reveal::Red(num), tag(" red")), value(Reveal::Green(num), tag(" green")), value(Reveal::Blue(num), tag(" blue")), ))(input) } fn reveal_group(input: &str) -> IResult<&str, RevealGroup> { let (input, reveals) = separated_list0(tag(", "), reveal)(input)?; Ok((input, RevealGroup::from(reveals))) } fn game(input: &str) -> IResult<&str, Game> { let (input, id) = int(tag("Game ")(input)?.0)?; let (input, reveal_groups) = separated_list0(tag("; "), reveal_group)(tag(": ")(input)?.0)?; Ok((input, Game { id, reveal_groups })) } struct Day2 {} impl Day2 {} impl AoCSolution for Day2 { type Input = String; type Solution = Solution; fn part1(input: Self::Input) -> Self::Solution { const MAX_RED: Solution = 12; const MAX_GREEN: Solution = 13; const MAX_BLUE: Solution = 14; let valid_reveal_group = |rg: &RevealGroup| { rg.red.unwrap_or(0) <= MAX_RED && rg.green.unwrap_or(0) <= MAX_GREEN && rg.blue.unwrap_or(0) <= MAX_BLUE }; input .lines() .map(|s| game(s).unwrap().1) .filter(|g| g.reveal_groups.iter().all(valid_reveal_group)) .map(|g| g.id) .sum() } fn part2(input: Self::Input) -> Self::Solution { input .lines() .map(|s| game(s).unwrap().1.made_possible().power()) .sum() } } #[cfg(test)] mod tests { use super::*; #[test] fn test() { assert_eq!( Day2::part1( r#"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"# .into() ), 8 ); assert_eq!( Day2::part2( r#"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"# .into() ), 2286 ); } }