day(19): add solution
Signed-off-by: Matej Focko <me@mfocko.xyz>
This commit is contained in:
parent
94a5b006aa
commit
ad42be0cb7
2 changed files with 249 additions and 0 deletions
2
samples/day19.txt
Normal file
2
samples/day19.txt
Normal file
|
@ -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.
|
247
src/bin/day19.rs
Normal file
247
src/bin/day19.rs
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
use std::{
|
||||||
|
cmp::max,
|
||||||
|
collections::{BTreeSet, BinaryHeap},
|
||||||
|
ops::{Add, Mul, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
use aoc_2022::*;
|
||||||
|
|
||||||
|
type Input = Vec<Blueprint>;
|
||||||
|
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<Material> 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<Material> 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<Material> 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<Robot>,
|
||||||
|
}
|
||||||
|
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<State> = 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<Input, Output> for Day19 {
|
||||||
|
fn parse_input<P: AsRef<Path>>(pathname: P) -> Input {
|
||||||
|
file_to_string(pathname)
|
||||||
|
.lines()
|
||||||
|
.map(|l| {
|
||||||
|
let numbers = BLUEPRINT_REGEX
|
||||||
|
.find_iter(l)
|
||||||
|
.map(|n| n.as_str().parse::<i32>().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);
|
Loading…
Reference in a new issue