const INPUT: &str = include_str!("../data/day2.txt"); const MAX_RED: usize = 12; const MAX_GREEN: usize = 13; const MAX_BLUE: usize = 14; pub fn day2a() -> String { let games = INPUT.lines().map(parse_game); format!( "{}", possible_games(games).fold(0, |acc, game| acc + game.0) ) } pub fn day2b() -> String { let games = INPUT.lines().map(parse_game); let power = games .into_iter() .map(|game| fewest_cubes(&game).power()) .fold(0, |acc, power| acc + power); format!("{}", power) } #[derive(Debug, Clone, PartialEq)] pub struct Game(usize, Vec); #[derive(Debug, Clone, PartialEq)] pub struct Info { red: usize, green: usize, blue: usize, } impl Info { fn power(&self) -> usize { self.red * self.green * self.blue } } pub(crate) fn possible_games(games: impl Iterator) -> impl Iterator { games.filter(|game| { game.1 .iter() .all(|info| info.red <= MAX_RED && info.green <= MAX_GREEN && info.blue <= MAX_BLUE) }) } pub(crate) fn fewest_cubes(game: &Game) -> Info { let mut min_red = 0; let mut min_green = 0; let mut min_blue = 0; for info in game.1.iter() { if info.red > min_red { min_red = info.red; } if info.green > min_green { min_green = info.green; } if info.blue > min_blue { min_blue = info.blue; } } Info { red: min_red, green: min_green, blue: min_blue, } } pub(crate) fn parse_game(input: &str) -> Game { let mut parts = input.split(':'); let number = parse_game_number(parts.next().unwrap()); let info_line = parts.next().unwrap().split(';'); let info = info_line .map(|block| parse_info(block)) .collect::>(); Game(number, info) } pub(crate) fn parse_game_number(input: &str) -> usize { let mut parts = input.split(' '); assert_eq!(parts.next(), Some("Game")); parts.next().unwrap().parse::().unwrap() } pub(crate) fn parse_info(input: &str) -> Info { let mut info = Info { red: 0, green: 0, blue: 0, }; for part in input.trim().split(',') { let mut sep = part.trim().split(' '); let count = sep.next().unwrap().trim(); let color = sep.next().unwrap().trim(); match color { "red" => info.red = count.parse::().unwrap(), "green" => info.green = count.parse::().unwrap(), "blue" => info.blue = count.parse::().unwrap(), _ => unreachable!(), } } info } #[cfg(test)] mod test { use super::*; const INPUT: &str = "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 parses_a_game() { let test_cases = [ ( "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", Game( 1, vec![ Info { red: 4, green: 0, blue: 3, }, Info { red: 1, green: 2, blue: 6, }, Info { red: 0, green: 2, blue: 0, }, ], ), "Game 1", ), ( "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", Game( 2, vec![ Info { red: 0, green: 2, blue: 1, }, Info { red: 1, green: 3, blue: 4, }, Info { red: 0, green: 1, blue: 1, }, ], ), "Game 2", ), ( "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", Game( 3, vec![ Info { red: 20, green: 8, blue: 6, }, Info { red: 4, green: 13, blue: 5, }, Info { red: 1, green: 5, blue: 0, }, ], ), "Game 3", ), ( "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", Game( 4, vec![ Info { red: 3, green: 1, blue: 6, }, Info { red: 6, green: 3, blue: 0, }, Info { red: 14, green: 3, blue: 15, }, ], ), "Game 4", ), ( "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", Game( 5, vec![ Info { red: 6, green: 3, blue: 1, }, Info { red: 1, green: 2, blue: 2, }, ], ), "Game 5", ), ]; for (input, expected, message) in test_cases { assert_eq!(parse_game(input), expected, "{}", message); } } #[test] fn possible_games() { let games = INPUT.lines().map(parse_game); let games = super::possible_games(games).collect::>(); assert_eq!(games.len(), 3); assert_eq!( games.into_iter().map(|g| g.0).collect::>(), vec![1, 2, 5] ); } #[test] fn fewest_cubes() { let test_cases = [ ( "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", Info { red: 4, green: 2, blue: 6, }, "Game 1", ), ( "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", Info { red: 1, green: 3, blue: 4, }, "Game 2", ), ( "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", Info { red: 20, green: 13, blue: 6, }, "Game 3", ), ( "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", Info { red: 14, green: 3, blue: 15, }, "Game 4", ), ( "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", Info { red: 6, green: 3, blue: 2, }, "Game 5", ), ]; for (input, expected, message) in test_cases { assert_eq!( super::fewest_cubes(&super::parse_game(input)), expected, "{}", message ); } } #[test] fn game_power() { let test_cases = [ ( "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green", 48, "Game 1", ), ( "Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue", 12, "Game 2", ), ( "Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red", 1560, "Game 3", ), ( "Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red", 630, "Game 4", ), ( "Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green", 36, "Game 5", ), ]; for (input, expected, message) in test_cases { assert_eq!( super::fewest_cubes(&super::parse_game(input)).power(), expected, "{}", message ); } } }