adventofcode23/src/day2.rs

119 lines
2.7 KiB
Rust

//! Day 2: Cube Conundrum
use once_cell::sync::Lazy;
use regex::Regex;
pub const DAY: u8 = 2;
pub const TITLE: &str = "Cube Conundrum";
#[derive(Debug)]
struct Game {
id: u32,
draws: Vec<Draw>,
}
#[derive(Debug, Default)]
struct Draw {
r: u32,
g: u32,
b: u32,
}
impl Draw {
fn max(&self, other: &Draw) -> Self {
Self {
r: self.r.max(other.r),
g: self.g.max(other.g),
b: self.b.max(other.b),
}
}
fn power(&self) -> u32 {
self.r * self.g * self.b
}
}
fn parse_games(input: Vec<String>) -> Vec<Game> {
static LINE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^Game (\d+): (.+)$").unwrap());
input
.iter()
.map(|line| {
let cap = LINE.captures(line).expect(line);
let id = cap[1].parse().unwrap();
let draws = cap[2]
.split("; ")
.map(|gstr| {
let (mut r, mut g, mut b) = (0, 0, 0);
for p in gstr.split(", ") {
if let Some(n) = p.strip_suffix(" red") {
r = n.parse().unwrap();
} else if let Some(n) = p.strip_suffix(" green") {
g = n.parse().unwrap();
} else if let Some(n) = p.strip_suffix(" blue") {
b = n.parse().unwrap();
} else {
panic!("invalid part: {p}")
}
}
Draw { r, g, b }
})
.collect();
Game { id, draws }
})
.collect()
}
pub fn part1(input: Vec<String>) -> String {
parse_games(input)
.iter()
.filter_map(|g| {
if g.draws.iter().all(|d| d.r <= 12 && d.g <= 13 && d.b <= 14) {
Some(g.id)
} else {
None
}
})
.sum::<u32>()
.to_string()
}
pub fn part2(input: Vec<String>) -> String {
parse_games(input)
.iter()
.map(|g| {
g.draws
.iter()
.fold(Draw::default(), |acc, d| acc.max(d))
.power()
})
.sum::<u32>()
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn t_example1() {
assert_eq!(part1(crate::read_example(DAY, 1)), "8");
}
#[test]
fn t_part1() {
assert_eq!(part1(crate::read_input(DAY)), "2006");
}
#[test]
fn t_example2() {
assert_eq!(part2(crate::read_example(DAY, 2)), "2286");
}
#[test]
fn t_part2() {
assert_eq!(part2(crate::read_input(DAY)), "84911");
}
}