diff --git a/samples/day19.txt b/samples/day19.txt new file mode 100644 index 0000000..f39c094 --- /dev/null +++ b/samples/day19.txt @@ -0,0 +1,2 @@ +Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian. +Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian. diff --git a/src/bin/day19.rs b/src/bin/day19.rs new file mode 100644 index 0000000..5abaa7f --- /dev/null +++ b/src/bin/day19.rs @@ -0,0 +1,247 @@ +use std::{ + cmp::max, + collections::{BTreeSet, BinaryHeap}, + ops::{Add, Mul, Sub}, +}; + +use aoc_2022::*; + +type Input = Vec; +type Output = i32; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +struct Material { + ore: i32, + clay: i32, + obsidian: i32, + geode: i32, +} +impl Material { + fn new(ore: i32, clay: i32, obsidian: i32, geode: i32) -> Self { + Self { + ore, + clay, + obsidian, + geode, + } + } + + fn nothing() -> Material { + Material::new(0, 0, 0, 0) + } + fn ore() -> Material { + Material::new(1, 0, 0, 0) + } + fn clay() -> Material { + Material::new(0, 1, 0, 0) + } + fn obsidian() -> Material { + Material::new(0, 0, 1, 0) + } + fn geode() -> Material { + Material::new(0, 0, 0, 1) + } + + fn is_le(&self, rhs: &Material) -> bool { + self.ore <= rhs.ore + && self.clay <= rhs.clay + && self.obsidian <= rhs.obsidian + && self.geode <= rhs.geode + } +} +impl Mul for i32 { + type Output = Material; + + fn mul(self, rhs: Material) -> Self::Output { + Material::new( + self * rhs.ore, + self * rhs.clay, + self * rhs.obsidian, + self * rhs.geode, + ) + } +} +impl Add for Material { + type Output = Material; + + fn add(self, rhs: Material) -> Self::Output { + Material::new( + self.ore + rhs.ore, + self.clay + rhs.clay, + self.obsidian + rhs.obsidian, + self.geode + rhs.geode, + ) + } +} +impl Sub for Material { + type Output = Material; + + fn sub(self, rhs: Material) -> Self::Output { + Material::new( + self.ore - rhs.ore, + self.clay - rhs.clay, + self.obsidian - rhs.obsidian, + self.geode - rhs.geode, + ) + } +} + +#[derive(Debug)] +struct Robot { + id: i32, + cost: Material, + produces: Material, +} + +#[derive(Debug)] +struct Blueprint { + id: i32, + robots: Vec, +} +impl Blueprint { + fn max_cost(&self) -> Material { + Material::new( + self.robots.iter().map(|r| r.cost.ore).max().unwrap(), + self.robots.iter().map(|r| r.cost.clay).max().unwrap(), + self.robots.iter().map(|r| r.cost.obsidian).max().unwrap(), + i32::MAX, + ) + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +struct State { + t: i32, + available: Material, + produces: Material, + not_built: i32, +} +impl State { + fn expected_geodes(&self) -> i32 { + self.available.geode + ((2 * self.produces.geode + self.t - 1) * self.t / 2) + } + + fn new(t: i32, available: Material, produces: Material, not_built: i32) -> (i32, State) { + let s = State { + t, + available, + produces, + not_built, + }; + let priority = s.expected_geodes(); + + (priority, s) + } + + fn worth(&self, bp: &Blueprint, robot: &Robot) -> bool { + (self.not_built & robot.id) == 0 && (self.produces + robot.produces).is_le(&bp.max_cost()) + } +} + +fn max_geodes(bp: &Blueprint, time: i32) -> i32 { + let mut q: BinaryHeap<(i32, State)> = BinaryHeap::new(); + let mut seen: BTreeSet = BTreeSet::new(); + + q.push(State::new(time, Material::nothing(), Material::ore(), 0)); + + let mut found_maximum = 0; + + while let Some((expected, state)) = q.pop() { + if expected < found_maximum { + break; + } + + if !seen.insert(state) { + continue; + } + + if state.t == 0 { + found_maximum = max(found_maximum, state.available.geode); + continue; + } + + let can_be_built = bp + .robots + .iter() + .filter(|r| r.cost.is_le(&state.available) && state.worth(bp, r)) + .collect_vec(); + + for robot in &can_be_built { + q.push(State::new( + state.t - 1, + state.available + state.produces - robot.cost, + state.produces + robot.produces, + 0, + )); + } + + q.push(State::new( + state.t - 1, + state.available + state.produces, + state.produces, + can_be_built.iter().map(|r| r.id).sum(), + )) + } + + found_maximum +} + +lazy_static! { + static ref BLUEPRINT_REGEX: Regex = Regex::new(r"(\d+)").unwrap(); +} +struct Day19; +impl Solution for Day19 { + fn parse_input>(pathname: P) -> Input { + file_to_string(pathname) + .lines() + .map(|l| { + let numbers = BLUEPRINT_REGEX + .find_iter(l) + .map(|n| n.as_str().parse::().unwrap()) + .collect_vec(); + let [id, or_ore, c_ore, ob_ore, ob_clay, g_ore, g_obsidian] = numbers[..] else {unreachable!()}; + + Blueprint { + id, + robots: vec![ + Robot { + id: 1, + produces: Material::ore(), + cost: or_ore * Material::ore(), + }, + Robot { + id: 2, + produces: Material::clay(), + cost: c_ore * Material::ore(), + }, + Robot { + id: 4, + produces: Material::obsidian(), + cost: ob_ore * Material::ore() + ob_clay * Material::clay(), + }, + Robot { + id: 8, + produces: Material::geode(), + cost: g_ore * Material::ore() + g_obsidian * Material::obsidian(), + }, + ], + } + }) + .collect_vec() + } + + fn part_1(input: &Input) -> Output { + input.iter().map(|bp| bp.id * max_geodes(bp, 24)).sum() + } + + fn part_2(input: &Input) -> Output { + input.iter().take(3).map(|bp| max_geodes(bp, 32)).product() + } +} + +fn main() -> Result<()> { + // Day19::run("sample") + Day19::main() +} + +test_sample!(day_19, Day19, 33, 3472);