Day 19, part 1
This commit is contained in:
parent
813c9f0612
commit
ab870a881e
26
src/bin/d19p1.rs
Normal file
26
src/bin/d19p1.rs
Normal 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
238
src/day19.rs
Normal 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
|
||||||
|
}
|
@ -8,6 +8,7 @@ pub mod day15;
|
|||||||
pub mod day16;
|
pub mod day16;
|
||||||
pub mod day17;
|
pub mod day17;
|
||||||
pub mod day18;
|
pub mod day18;
|
||||||
|
pub mod day19;
|
||||||
pub mod day2;
|
pub mod day2;
|
||||||
pub mod day3;
|
pub mod day3;
|
||||||
pub mod day4;
|
pub mod day4;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user