Day 19, part 1
This commit is contained in:
parent
813c9f0612
commit
ab870a881e
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue