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