2022 - Day 19

This commit is contained in:
Dreaded_X 2022-12-20 04:05:56 +01:00
parent 8915fb135e
commit 9e3de56bdf
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
3 changed files with 344 additions and 0 deletions

30
2022/input/19/input Normal file
View File

@ -0,0 +1,30 @@
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 3 ore and 9 obsidian.
Blueprint 3: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 4 ore and 18 obsidian.
Blueprint 4: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 20 obsidian.
Blueprint 5: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 4 ore and 16 obsidian.
Blueprint 6: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian.
Blueprint 7: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 2 ore and 18 obsidian.
Blueprint 8: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 8 obsidian.
Blueprint 9: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 9 clay. Each geode robot costs 4 ore and 16 obsidian.
Blueprint 10: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 2 ore and 20 obsidian.
Blueprint 11: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian.
Blueprint 12: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 3 ore and 8 obsidian.
Blueprint 13: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 4 ore and 20 obsidian.
Blueprint 14: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 3 ore and 11 obsidian.
Blueprint 15: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 4 ore and 9 obsidian.
Blueprint 16: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 16 obsidian.
Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 10 clay. Each geode robot costs 2 ore and 14 obsidian.
Blueprint 18: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 3 ore and 12 obsidian.
Blueprint 19: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 3 ore and 18 obsidian.
Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 3 ore and 13 obsidian.
Blueprint 21: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 4 ore and 18 obsidian.
Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 3 ore and 19 obsidian.
Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 4 ore and 11 obsidian.
Blueprint 24: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 2 ore and 9 obsidian.
Blueprint 25: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 2 ore and 16 obsidian.
Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 2 ore and 20 obsidian.
Blueprint 27: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 20 obsidian.
Blueprint 28: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 19 obsidian.
Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 14 clay. Each geode robot costs 3 ore and 16 obsidian.
Blueprint 30: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 7 clay. Each geode robot costs 2 ore and 16 obsidian.

2
2022/input/19/test-1 Normal file
View 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.

312
2022/src/bin/day19.rs Normal file
View File

@ -0,0 +1,312 @@
#![feature(test)]
use std::{ops::{AddAssign, SubAssign}, str::FromStr};
use anyhow::Result;
use aoc::Solver;
// -- Runners --
fn main() -> Result<()> {
Day::solve()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn part1_test1() -> Result<()> {
Day::test(Day::part1, "test-1", 33)
}
#[test]
fn part1_solution() -> Result<()> {
Day::test(Day::part1, "input", 600)
}
#[test]
fn part2_test1() -> Result<()> {
Day::test(Day::part2, "test-1", 56*62)
}
#[test]
fn part2_solution() -> Result<()> {
Day::test(Day::part2, "input", 6000)
}
// Benchmarks
extern crate test;
#[bench]
#[ignore]
fn part1_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part1, b)
}
#[bench]
#[ignore]
fn part2_bench(b: &mut test::Bencher) {
Day::benchmark(Day::part2, b)
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
struct Resources {
ore: isize,
clay: isize,
obsidian: isize,
}
impl Resources {
fn new(ore: isize, clay: isize, obsidian: isize) -> Self {
Self { ore, clay, obsidian }
}
fn enough_for(&self, other: &Resources) -> bool {
self.ore >= other.ore &&
self.clay >= other.clay &&
self.obsidian >= other.obsidian
}
}
impl AddAssign for Resources {
fn add_assign(&mut self, rhs: Self) {
self.ore += rhs.ore;
self.clay += rhs.clay;
self.obsidian += rhs.obsidian;
}
}
impl SubAssign for Resources {
fn sub_assign(&mut self, rhs: Self) {
self.ore -= rhs.ore;
self.clay -= rhs.clay;
self.obsidian -= rhs.obsidian;
}
}
#[derive(Debug)]
struct Blueprint {
ore_robot_cost: Resources,
clay_robot_cost: Resources,
obsidian_robot_cost: Resources,
geode_robot_cost: Resources,
max: Resources,
}
impl Blueprint {
fn new(ore_robot_cost: Resources, clay_robot_cost: Resources, obsidian_robot_cost: Resources, geode_robot_cost: Resources) -> Self {
// Calculate how much of each resource we need at most every minute in order to construct
// every robot
let mut max = Resources::new(0, 0, 0);
max.ore = max.ore.max(ore_robot_cost.ore).max(clay_robot_cost.ore).max(obsidian_robot_cost.ore).max(geode_robot_cost.ore);
max.clay = max.clay.max(ore_robot_cost.clay).max(clay_robot_cost.clay).max(obsidian_robot_cost.clay).max(geode_robot_cost.clay);
max.obsidian = max.obsidian.max(ore_robot_cost.obsidian).max(clay_robot_cost.obsidian).max(obsidian_robot_cost.obsidian).max(geode_robot_cost.obsidian);
Self { ore_robot_cost, clay_robot_cost, obsidian_robot_cost, geode_robot_cost, max }
}
}
impl FromStr for Blueprint {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(" ");
let ore = Resources::new(
split.nth(6).unwrap().parse()?,
0,
0,
);
let clay = Resources::new(
split.nth(5).unwrap().parse()?,
0,
0,
);
let obsidian = Resources::new(
split.nth(5).unwrap().parse()?,
split.nth(2).unwrap().parse()?,
0,
);
let geode = Resources::new(
split.nth(5).unwrap().parse()?,
0,
split.nth(2).unwrap().parse()?,
);
Ok(Blueprint::new(ore, clay, obsidian, geode))
}
}
impl Blueprint {
fn visit(&self, state: State, mut best: isize) -> isize {
// If we have run out of time return the amount of geodes we have broken in total
if state.time_remaining <= 0 {
return state.geodes;
}
// Using n(n+1)/2 calculate the maximum possible geodes that we can crack
// If we can not improve the best so far, we end here
// !!! This make a massive difference, from who knows how long to less then a second
if state.geodes + (state.time_remaining-1) * state.time_remaining / 2 < best {
return 0;
}
// Given the remaining time, calculate how much of the resource we still need in the worst
// case scenerio. If current stockpile + future production if lower we will attempt to
// construct a robot, otherwise we do not need them anymore
// === Ore ===
if state.robots.ore * state.time_remaining + state.resources.ore < self.max.ore * state.time_remaining {
// Check if we can construct one right now
if state.resources.enough_for(&self.ore_robot_cost) {
// Create a new state
let mut next = state.next();
// Substract the resources
next.resources -= self.ore_robot_cost;
// Add the robot
next.robots.ore += 1;
best = best.max(self.visit(next, best));
} else {
// If we can not construct one right now, skip to the next point in time that we
// can build one
let mut next = state.next();
while !next.resources.enough_for(&self.ore_robot_cost) && next.time_remaining >= 0 {
next = next.next();
}
best = best.max(self.visit(next, best));
}
}
// === Clay ===
if state.robots.clay * state.time_remaining + state.resources.clay < self.max.clay * state.time_remaining {
if state.resources.enough_for(&self.clay_robot_cost) {
// Create a new state
let mut next = state.next();
// Substract the resources
next.resources -= self.clay_robot_cost;
// Add the robot
next.robots.clay += 1;
best = best.max(self.visit(next, best));
} else {
// If we can not construct one right now, skip to the next point in time that we
// can build one
let mut next = state.next();
while !next.resources.enough_for(&self.clay_robot_cost) && next.time_remaining >= 0 {
next = next.next();
}
best = best.max(self.visit(next, best));
}
}
// === Obsidian ===
if state.robots.obsidian * state.time_remaining + state.resources.obsidian < self.max.obsidian * state.time_remaining {
if state.resources.enough_for(&self.obsidian_robot_cost) {
// Create a new state
let mut next = state.next();
// Substract the resources
next.resources -= self.obsidian_robot_cost;
// Add the robot
next.robots.obsidian += 1;
best = best.max(self.visit(next, best));
} else {
// If we can not construct one right now, skip to the next point in time that we
// can build one
let mut next = state.next();
while !next.resources.enough_for(&self.obsidian_robot_cost) && next.time_remaining >= 0 {
next = next.next();
}
best = best.max(self.visit(next, best));
}
}
// === Geode ==
// There is no upper limit to producing robots, so only check if we can make one
if state.resources.enough_for(&self.geode_robot_cost) {
// Create a new state
let mut next = state.next();
// Substract the resources
next.resources -= self.geode_robot_cost;
// Since we never use the geodes we can add all that this robot will mine at once
next.geodes += state.time_remaining - 1;
best = best.max(self.visit(next, best));
} else {
// If we can not construct one right now, skip to the next point in time that we
// can build one
let mut next = state.next();
while !next.resources.enough_for(&self.geode_robot_cost) && next.time_remaining >= 0 {
next = next.next();
}
best = best.max(self.visit(next, best));
}
return best;
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
struct State {
resources: Resources,
robots: Resources,
geodes: isize,
time_remaining: isize,
}
impl State {
fn new(time_remaining: isize) -> Self {
Self {
resources: Resources::new(0, 0, 0),
robots: Resources::new(1, 0, 0),
geodes: 0,
time_remaining,
}
}
fn next(&self) -> Self {
let mut next = self.clone();
// Update the time
next.time_remaining -= 1;
// Collect resources
next.resources += self.robots;
return next;
}
}
// -- Solution --
pub struct Day;
impl aoc::Solver for Day {
type Output1 = isize;
type Output2 = isize;
fn day() -> u8 {
19
}
fn part1(input: &str) -> Self::Output1 {
let blueprints = input.trim().lines().flat_map(Blueprint::from_str).collect::<Vec<_>>();
let state = State::new(24);
blueprints.iter()
.enumerate()
.map(|(idx, blueprint)| {
blueprint.visit(state.clone(), 0) * (idx as isize + 1)
}).sum()
}
fn part2(input: &str) -> Self::Output2 {
let blueprints = input.trim().lines().flat_map(Blueprint::from_str).collect::<Vec<_>>();
let state = State::new(32);
blueprints.iter()
.take(3)
.fold(1, |acc, blueprint| {
acc * blueprint.visit(state.clone(), 0)
})
}
}