Day 19, part 1

This commit is contained in:
jazzpi 2022-12-21 13:39:43 +01:00
parent 813c9f0612
commit ab870a881e
3 changed files with 265 additions and 0 deletions

26
src/bin/d19p1.rs Normal file
View File

@ -0,0 +1,26 @@
use std::thread;
use aoc22::{day19, util};
const MINUTES: usize = 24;
pub fn main() {
let blueprints = day19::parse_blueprints(&util::parse_input());
let mut handles = Vec::new();
for (i, blueprint) in blueprints.iter().enumerate() {
let blueprint = blueprint.clone();
handles.push((
i,
thread::spawn(move || day19::max_geodes(MINUTES, &blueprint)),
));
}
let mut sum = 0;
for (i, handle) in handles {
let max = handle.join().unwrap();
sum += (i + 1) * max;
}
println!("Sum of quality scores: {}", sum);
}

238
src/day19.rs Normal file
View File

@ -0,0 +1,238 @@
use std::collections::HashSet;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct Blueprint {
pub ore_ore_cost: usize,
pub clay_ore_cost: usize,
pub obsidian_ore_cost: usize,
pub obsidian_clay_cost: usize,
pub geode_ore_cost: usize,
pub geode_obsidian_cost: usize,
}
pub fn parse_blueprints(input: &String) -> Vec<Blueprint> {
let re = Regex::new(
r"Blueprint (\d+): Each ore robot costs (\d+) ore. Each clay robot costs (\d+) ore. Each obsidian robot costs (\d+) ore and (\d+) clay. Each geode robot costs (\d+) ore and (\d+) obsidian.",
).unwrap();
input
.lines()
.enumerate()
.map(|(i, line)| {
let captures = re
.captures(line)
.unwrap_or_else(|| panic!("Invalid blueprint formatting:\n{}", line));
assert_eq!(
captures.get(1).unwrap().as_str().parse::<usize>().unwrap(),
i + 1
);
let ore_ore_cost = captures.get(2).unwrap().as_str().parse().unwrap();
let clay_ore_cost = captures.get(3).unwrap().as_str().parse().unwrap();
let obsidian_ore_cost = captures.get(4).unwrap().as_str().parse().unwrap();
let obsidian_clay_cost = captures.get(5).unwrap().as_str().parse().unwrap();
let geode_ore_cost = captures.get(6).unwrap().as_str().parse().unwrap();
let geode_obsidian_cost = captures.get(7).unwrap().as_str().parse().unwrap();
Blueprint {
ore_ore_cost,
clay_ore_cost,
obsidian_ore_cost,
obsidian_clay_cost,
geode_ore_cost,
geode_obsidian_cost,
}
})
.collect()
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct State {
pub time_remaining: usize,
pub ore: usize,
pub clay: usize,
pub obsidian: usize,
pub geodes: usize,
pub ore_robots: usize,
pub clay_robots: usize,
pub obsidian_robots: usize,
pub geode_robots: usize,
}
impl State {
pub fn new(initial_time: usize) -> State {
State {
time_remaining: initial_time,
ore: 0,
clay: 0,
obsidian: 0,
geodes: 0,
ore_robots: 1,
clay_robots: 0,
obsidian_robots: 0,
geode_robots: 0,
}
}
pub fn finished(&self) -> bool {
self.time_remaining == 0
}
pub fn possible_actions(&self, blueprint: &Blueprint) -> Vec<State> {
assert!(!self.finished());
let mut result = Vec::new();
if self.time_remaining > 1 {
self.produce_ore_next(blueprint).map(|a| result.push(a));
self.produce_clay_next(blueprint).map(|a| result.push(a));
self.produce_obsidian_next(blueprint)
.map(|a| result.push(a));
self.produce_geode_next(blueprint).map(|a| result.push(a));
}
let mut do_nothing = self.clone();
do_nothing.run_steps(self.time_remaining);
result.push(do_nothing);
result
}
fn produce<F: Fn(&mut State)>(&self, time_for_ore_prod: isize, produce: F) -> Option<State> {
let time_until_robot_ready = (time_for_ore_prod.max(0) as usize) + 1;
// For this to make sense, we also need at least one minute
// remaining after producing the robot
if time_until_robot_ready + 1 > self.time_remaining {
return None;
}
let mut produced = self.clone();
produced.run_steps(time_until_robot_ready);
produce(&mut produced);
Some(produced)
}
fn produce_ore_next(&self, blueprint: &Blueprint) -> Option<State> {
if self.ore_robots == 0 {
return None;
}
let time_required = num::Integer::div_ceil(
&((blueprint.ore_ore_cost as isize) - (self.ore as isize)),
&(self.ore_robots as isize),
);
self.produce(time_required, |mut s| {
s.ore_robots += 1;
s.ore -= blueprint.ore_ore_cost;
})
}
fn produce_clay_next(&self, blueprint: &Blueprint) -> Option<State> {
if self.ore_robots == 0 {
return None;
}
let time_required = num::Integer::div_ceil(
&((blueprint.clay_ore_cost as isize) - (self.ore as isize)),
&(self.ore_robots as isize),
);
self.produce(time_required, |mut s| {
s.clay_robots += 1;
s.ore -= blueprint.clay_ore_cost;
})
}
fn produce_obsidian_next(&self, blueprint: &Blueprint) -> Option<State> {
if self.ore_robots == 0 || self.clay_robots == 0 {
return None;
}
let time_required_ore = num::Integer::div_ceil(
&((blueprint.obsidian_ore_cost as isize) - (self.ore as isize)),
&(self.ore_robots as isize),
);
let time_required_clay = num::Integer::div_ceil(
&((blueprint.obsidian_clay_cost as isize) - (self.clay as isize)),
&(self.clay_robots as isize),
);
self.produce(time_required_ore.max(time_required_clay), |mut s| {
s.obsidian_robots += 1;
s.ore -= blueprint.obsidian_ore_cost;
s.clay -= blueprint.obsidian_clay_cost;
})
}
fn produce_geode_next(&self, blueprint: &Blueprint) -> Option<State> {
if self.ore_robots == 0 || self.obsidian_robots == 0 {
return None;
}
let time_required_ore = num::Integer::div_ceil(
&((blueprint.geode_ore_cost as isize) - (self.ore as isize)),
&(self.ore_robots as isize),
);
let time_required_obsidian = num::Integer::div_ceil(
&((blueprint.geode_obsidian_cost as isize) - (self.obsidian as isize)),
&(self.obsidian_robots as isize),
);
self.produce(time_required_ore.max(time_required_obsidian), |mut s| {
s.geode_robots += 1;
s.ore -= blueprint.geode_ore_cost;
s.obsidian -= blueprint.geode_obsidian_cost;
})
}
pub fn upper_bound(&self) -> usize {
// Build one geode robot each remaining turn
// \sum_{k=1}^n {k - 1} = \sum_{k=0}^{n-1} {k} = 1/2 (n-1) n
self.lower_bound() + ((self.time_remaining - 1) * self.time_remaining) / 2
}
pub fn lower_bound(&self) -> usize {
self.geodes + self.geode_robots * self.time_remaining
}
fn run_steps(&mut self, n: usize) {
assert!(self.time_remaining >= n);
self.time_remaining -= n;
self.ore += self.ore_robots * n;
self.clay += self.clay_robots * n;
self.obsidian += self.obsidian_robots * n;
self.geodes += self.geode_robots * n;
}
}
pub fn max_geodes(minutes: usize, blueprint: &Blueprint) -> usize {
let initial = State::new(minutes);
let initial_upper = initial.upper_bound();
let mut next = Vec::new();
next.push((initial.clone(), initial_upper));
let mut visited = HashSet::new();
visited.insert(initial);
let mut lower_bound = 0;
while let Some(n) = next.pop() {
if n.1 < lower_bound {
// Between pushing this state and popping it, we've found a better lower bound
continue;
}
let state = n.0;
for action in state.possible_actions(blueprint) {
if action.finished() {
let action_lower = action.lower_bound();
if action_lower > lower_bound {
lower_bound = action_lower;
}
} else {
let action_upper = action.upper_bound();
if action_upper > lower_bound && !visited.contains(&action) {
next.push((action.clone(), action_upper));
visited.insert(action);
}
}
}
}
lower_bound
}

View File

@ -8,6 +8,7 @@ pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;
pub mod day2;
pub mod day3;
pub mod day4;