advent-of-code/2023/src/day2.rs

358 lines
9.8 KiB
Rust

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<Info>);
#[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<Item = Game>) -> impl Iterator<Item = Game> {
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::<Vec<Info>>();
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::<usize>().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::<usize>().unwrap(),
"green" => info.green = count.parse::<usize>().unwrap(),
"blue" => info.blue = count.parse::<usize>().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::<Vec<Game>>();
assert_eq!(games.len(), 3);
assert_eq!(
games.into_iter().map(|g| g.0).collect::<Vec<usize>>(),
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
);
}
}
}