This commit is contained in:
2022-12-19 23:28:17 +01:00
parent 8cb4f331f1
commit dee8ebe78e
5 changed files with 261 additions and 0 deletions

30
inputs/day19.txt 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 7 clay. Each geode robot costs 2 ore and 19 obsidian.
Blueprint 2: 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 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 10 obsidian.
Blueprint 4: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 2 ore and 12 obsidian.
Blueprint 5: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 3 ore and 14 obsidian.
Blueprint 6: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 3 ore and 7 obsidian.
Blueprint 7: 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 8: 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 9: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 8 clay. Each geode robot costs 2 ore and 14 obsidian.
Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 11 clay. Each geode robot costs 3 ore and 14 obsidian.
Blueprint 11: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 4 ore and 8 obsidian.
Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 18 obsidian.
Blueprint 13: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 11 clay. Each geode robot costs 2 ore and 10 obsidian.
Blueprint 14: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 17 obsidian.
Blueprint 15: 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 3 ore and 17 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 17 obsidian.
Blueprint 17: 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 4 ore and 8 obsidian.
Blueprint 18: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 9 clay. Each geode robot costs 3 ore and 9 obsidian.
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 3 ore and 14 obsidian.
Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 13 clay. Each geode robot costs 3 ore and 12 obsidian.
Blueprint 21: 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 22: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian.
Blueprint 24: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 3 ore and 8 obsidian.
Blueprint 25: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 16 obsidian.
Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 7 obsidian.
Blueprint 27: 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 3 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 10 clay. Each geode robot costs 2 ore and 10 obsidian.
Blueprint 29: 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 2 ore and 13 obsidian.
Blueprint 30: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 4 ore and 8 obsidian.

8
src/bin/day19_1.rs Normal file
View File

@@ -0,0 +1,8 @@
use std::fs;
use aoc2022::day19::process_part_1;
fn main() {
let file = fs::read_to_string("./inputs/day19.txt").unwrap();
println!("{}", process_part_1(&file));
}

8
src/bin/day19_2.rs Normal file
View File

@@ -0,0 +1,8 @@
use std::fs;
use aoc2022::day19::process_part_2;
fn main() {
let file = fs::read_to_string("./inputs/day19.txt").unwrap();
println!("{}", process_part_2(&file));
}

214
src/day19.rs Normal file
View File

@@ -0,0 +1,214 @@
use std::collections::HashSet;
use nom::bytes::complete::tag;
use nom::character::complete;
use nom::character::complete::newline;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::sequence::{delimited, preceded, separated_pair, tuple};
use nom::IResult;
use rayon::prelude::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
pub fn process_part_1(input: &str) -> usize {
let blueprints = parse_input(input).unwrap().1;
blueprints
.par_iter()
.map(|bp| bp.id * find_best_geodes(bp, 24))
.sum()
}
pub fn process_part_2(input: &str) -> usize {
let blueprints = parse_input(input).unwrap().1;
blueprints
.par_iter()
.take(3)
.map(|bp| find_best_geodes(bp, 32))
.product()
}
#[derive(Debug)]
struct Blueprint {
id: usize,
ore_bot_cost: usize,
clay_bot_cost: usize,
obsidian_bot_cost: (usize, usize),
geode_bot_cost: (usize, usize),
}
impl Blueprint {
fn max_ore_cost(&self) -> usize {
[
self.ore_bot_cost,
self.clay_bot_cost,
self.obsidian_bot_cost.0,
self.geode_bot_cost.0,
]
.into_iter()
.max()
.unwrap()
}
}
#[derive(Default, Copy, Clone, Debug, Hash, Eq, PartialEq)]
struct State {
time: usize,
ore: usize,
clay: usize,
obsidian: usize,
geode: usize,
ore_bots: usize,
clay_bots: usize,
obsidian_bots: usize,
geode_bots: usize,
}
impl State {
fn upper_limit_geodes(&self) -> usize {
// assumes we can create a bot per every minute
let build_geode_cap = (self.time * (self.time + 1)) / 2;
self.geode + build_geode_cap + self.geode_bots * self.time
}
fn tick(&self) -> Self {
State {
ore: self.ore + self.ore_bots,
clay: self.clay + self.clay_bots,
obsidian: self.obsidian + self.obsidian_bots,
geode: self.geode + self.geode_bots,
time: self.time - 1,
..*self
}
}
}
fn find_best_geodes(bp: &Blueprint, time: usize) -> usize {
let mut visited = HashSet::new();
let mut stack = vec![State {
ore_bots: 1,
time,
..Default::default()
}];
let max_ore_cost = bp.max_ore_cost();
let mut max_geodes = 0;
while let Some(state) = stack.pop() {
if state.time == 0 {
max_geodes = max_geodes.max(state.geode);
continue;
}
// Prune states where its immediately obvious we won't beat the maximum
if state.upper_limit_geodes() < max_geodes {
continue;
}
// Best case, we can build a geode bot:
if state.ore >= bp.geode_bot_cost.0 && state.obsidian >= bp.geode_bot_cost.1 {
let next = State {
geode_bots: state.geode_bots + 1,
ore: state.ore - bp.geode_bot_cost.0 + state.ore_bots,
obsidian: state.obsidian - bp.geode_bot_cost.1 + state.obsidian_bots,
..state.tick()
};
if visited.insert(next.clone()) {
stack.push(next);
}
}
// Obsidian bots are nice too
if state.ore >= bp.obsidian_bot_cost.0 && state.clay >= bp.obsidian_bot_cost.1 {
let next = State {
obsidian_bots: state.obsidian_bots + 1,
ore: state.ore - bp.obsidian_bot_cost.0 + state.ore_bots,
clay: state.clay - bp.obsidian_bot_cost.1 + state.clay_bots,
..state.tick()
};
if visited.insert(next.clone()) {
stack.push(next);
}
}
// Clay?
if state.ore >= bp.clay_bot_cost {
let next = State {
clay_bots: state.clay_bots + 1,
ore: state.ore - bp.clay_bot_cost + state.ore_bots,
..state.tick()
};
if visited.insert(next.clone()) {
stack.push(next);
}
}
// Ore? But only if we don't make enough to make any new bot per round we want
if state.ore >= bp.ore_bot_cost && state.ore_bots < max_ore_cost {
let next = State {
ore_bots: state.ore_bots + 1,
ore: state.ore - bp.ore_bot_cost + state.ore_bots,
..state.tick()
};
if visited.insert(next.clone()) {
stack.push(next);
}
}
// Final option, do nothing
let next = state.tick();
if visited.insert(next.clone()) {
stack.push(next);
}
}
max_geodes
}
fn parse_input(input: &str) -> IResult<&str, Vec<Blueprint>> {
separated_list1(newline, parse_blueprint)(input)
}
fn parse_blueprint(input: &str) -> IResult<&str, Blueprint> {
map(
tuple((
preceded(tag("Blueprint "), complete::u64),
preceded(tag(": Each ore robot costs "), complete::u64),
preceded(tag(" ore. Each clay robot costs "), complete::u64),
preceded(
tag(" ore. Each obsidian robot costs "),
separated_pair(complete::u64, tag(" ore and "), complete::u64),
),
delimited(
tag(" clay. Each geode robot costs "),
separated_pair(complete::u64, tag(" ore and "), complete::u64),
tag(" obsidian."),
),
)),
|(id, ore_bot, clay_bot, (ob_ore, ob_clay), (gb_ore, gb_obs))| Blueprint {
id: id as usize,
ore_bot_cost: ore_bot as usize,
clay_bot_cost: clay_bot as usize,
obsidian_bot_cost: (ob_ore as usize, ob_clay as usize),
geode_bot_cost: (gb_ore as usize, gb_obs as usize),
},
)(input)
}
#[cfg(test)]
mod tests {
use super::*;
const INPUT: &str = "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.";
#[test]
fn day1() {
assert_eq!(process_part_1(INPUT), 33);
}
#[test]
fn day2() {
assert_eq!(process_part_2(INPUT), 3472);
}
}

View File

@@ -19,3 +19,4 @@ pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;
pub mod day19;