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

217 lines
5.2 KiB
Rust

use std::collections::HashMap;
const FILE_STR: &'static str = include_str!("input.txt");
#[derive(Debug, PartialOrd, PartialEq)]
struct CubeSet {
red: usize,
green: usize,
blue: usize,
}
impl CubeSet {
fn new(red: usize, green: usize, blue: usize) -> Self {
CubeSet { red, green, blue }
}
}
type CubeMap = HashMap<usize, Vec<CubeSet>>;
type MinCubeMap = HashMap<usize, CubeSet>;
fn parse_colors(raw_colors: Vec<&str>) -> CubeSet {
let mut red = 0usize;
let mut green = 0usize;
let mut blue = 0usize;
for raw_color in raw_colors {
let color_split: Vec<&str> = raw_color
.split(' ')
.map(|s| s.trim())
.filter(|s| s.len() > 0)
.collect();
let [num_str, color] = color_split[..] else {
panic!("Bad color row")
};
let num = num_str.parse::<usize>().unwrap();
match color {
"red" => {
red = num;
}
"blue" => {
blue = num;
}
"green" => {
green = num;
}
_ => {}
}
}
CubeSet::new(red, green, blue)
}
fn parse_game(line: &str, map: &mut CubeMap) {
let mut draws: Vec<CubeSet> = Vec::new();
let raw: Vec<&str> = line.split(": ").map(|s| s.trim()).collect();
if raw.len() < 2 {
return;
}
let [raw_id, raw_game] = raw[..] else {
panic!("Bad input row")
};
let id = raw_id.replace("Game ", "").parse::<usize>().unwrap();
let pulls: Vec<&str> = raw_game.split(';').collect();
for i in 0..pulls.len() {
let raw_colors = pulls[i].split(", ").collect();
draws.push(parse_colors(raw_colors));
}
map.insert(id, draws);
}
fn is_valid(reference: &CubeSet, comparison: &CubeSet) -> bool {
reference.green >= comparison.green
&& reference.blue >= comparison.blue
&& reference.red >= comparison.red
}
fn validate_games(reference: &CubeSet, games: CubeMap) -> Vec<usize> {
games
.into_iter()
.filter(|(_, cubes)| cubes.iter().all(|c| is_valid(reference, c)))
.map(|(id, _)| id)
.collect()
}
fn find_min_cubes(valid_games: &CubeMap) -> MinCubeMap {
let mut min_cubes = HashMap::new();
valid_games.iter().for_each(|(id, set_map)| {
let mut r = 0usize;
let mut g = 0usize;
let mut b = 0usize;
set_map.iter().for_each(|set| {
if set.red > r {
r = set.red;
}
if set.green > g {
g = set.green;
}
if set.blue > b {
b = set.blue;
}
});
min_cubes.insert(*id, CubeSet::new(r, g, b));
});
min_cubes
}
fn cube_power(cube: &CubeSet) -> usize {
cube.red * cube.green * cube.blue
}
fn part_one() {
let mut map: CubeMap = HashMap::new();
let reference = CubeSet {
red: 12,
green: 13,
blue: 14,
};
FILE_STR
.split('\n')
.for_each(|line| parse_game(line, &mut map));
let valid = validate_games(&reference, map);
let sum: usize = valid.into_iter().sum();
println!("Part 1 Sum of valid games: {}", sum);
}
fn part_two() {
let mut map: CubeMap = HashMap::new();
FILE_STR
.split('\n')
.for_each(|line| parse_game(line, &mut map));
let min_cube_sum: usize = find_min_cubes(&map)
.iter()
.map(|(_, set)| cube_power(set))
.sum();
println!("Part 2 Sum of min cubes: {}", min_cube_sum);
}
fn main() {
part_one();
part_two();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_min_cubes() {
let games: CubeMap = HashMap::from([
(
1,
Vec::from([
CubeSet::new(4, 0, 3),
CubeSet::new(1, 2, 0),
CubeSet::new(0, 2, 6),
]),
),
(
2,
Vec::from([
CubeSet::new(0, 2, 1),
CubeSet::new(1, 3, 4),
CubeSet::new(0, 1, 1),
]),
),
(
3,
Vec::from([
CubeSet::new(20, 8, 6),
CubeSet::new(4, 13, 5),
CubeSet::new(1, 5, 0),
]),
),
(
4,
Vec::from([
CubeSet::new(3, 1, 6),
CubeSet::new(6, 3, 0),
CubeSet::new(14, 3, 15),
]),
),
(5, Vec::from([CubeSet::new(6, 3, 1), CubeSet::new(1, 2, 2)])),
]);
let expected: MinCubeMap = HashMap::from([
(1, CubeSet::new(4, 2, 6)),
(2, CubeSet::new(1, 3, 4)),
(3, CubeSet::new(20, 13, 6)),
(4, CubeSet::new(14, 3, 15)),
(5, CubeSet::new(6, 3, 2)),
]);
let actual = find_min_cubes(&games);
actual.iter().for_each(|(id, min_cubes)| {
assert_eq!(
min_cubes,
expected.get(id).unwrap(),
"Game {} min cubes",
id
);
});
}
}