diff --git a/2022/Cargo.toml b/2022/Cargo.toml index c067e58..bedb95f 100644 --- a/2022/Cargo.toml +++ b/2022/Cargo.toml @@ -9,6 +9,7 @@ impl_ops = "0.1.1" itertools = "0.10.5" paste = "1.0" rayon = "1.6.0" +scanf = "1.2.1" [profile.bench] lto = true diff --git a/2022/inputs/day19 b/2022/inputs/day19 new file mode 100644 index 0000000..a3a07c5 --- /dev/null +++ b/2022/inputs/day19 @@ -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 14 clay. Each geode robot costs 2 ore and 16 obsidian. +Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 7 obsidian. +Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian. +Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian. +Blueprint 5: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 6: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 4 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 9 clay. Each geode robot costs 2 ore and 20 obsidian. +Blueprint 8: 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 9: 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 10: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 4 ore and 16 obsidian. +Blueprint 11: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 3 ore and 15 obsidian. +Blueprint 12: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 8 obsidian. +Blueprint 13: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 17 obsidian. +Blueprint 14: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 10 obsidian. +Blueprint 15: 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 16: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 20 obsidian. +Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 12 clay. Each geode robot costs 3 ore and 15 obsidian. +Blueprint 18: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 13 obsidian. +Blueprint 19: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 18 clay. Each geode robot costs 2 ore and 19 obsidian. +Blueprint 20: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 9 obsidian. +Blueprint 21: 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 22: 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 23: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 6 clay. Each geode robot costs 4 ore and 11 obsidian. +Blueprint 24: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 25: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 14 obsidian. +Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 15 clay. Each geode robot costs 4 ore and 16 obsidian. +Blueprint 27: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 11 obsidian. +Blueprint 28: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 4 ore and 17 obsidian. +Blueprint 29: 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 3 ore and 14 obsidian. +Blueprint 30: 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 4 ore and 14 obsidian. diff --git a/2022/src/bin/day19.rs b/2022/src/bin/day19.rs new file mode 100644 index 0000000..7029e55 --- /dev/null +++ b/2022/src/bin/day19.rs @@ -0,0 +1,137 @@ +#![feature(test)] +extern crate test; +use std::ops::Sub; + +use aoc2022::{boilerplate, common::*}; +use scanf::sscanf; + +const DAY: usize = 19; +type Parsed = Vec; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +struct Factory { + minute: u32, + ore: u32, + ore_robot: u32, + clay: u32, + clay_robot: u32, + obsidian: u32, + obsidian_robot: u32, + geode: u32, + geode_robot: u32, +} + +impl Factory { + fn pass_minute(self) -> Self { + Factory { + ore: self.ore + self.ore_robot, + clay: self.clay + self.clay_robot, + obsidian: self.obsidian + self.obsidian_robot, + geode: self.geode + self.geode_robot, + minute: self.minute + 1, + ..self + } + } + + fn can_afford(&self, price: &Price) -> bool { + self.ore >= price.ore && self.clay >= price.clay && self.obsidian >= price.obsidian + } +} + +impl Sub for Factory { + type Output = Self; + + fn sub(self, price: Price) -> Self::Output { + Factory { ore: self.ore - price.ore, clay: self.clay - price.clay, obsidian: self.obsidian - price.obsidian, ..self } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +struct Blueprint { + number: u32, + ore_robot: Price, + clay_robot: Price, + obsidian_robot: Price, + geode_robot: Price, + max_ore: u32, +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +struct Price { + ore: u32, + clay: u32, + obsidian: u32, +} + +fn parse_input(raw: &str) -> Parsed { + raw.lines().map(|line| { + let mut bp = Blueprint::default(); + let mut number = 0; + let mut ore_robot_ore = 0; + let mut clay_robot_ore = 0; + let mut obsidian_robot_ore = 0; + let mut obsidian_robot_clay = 0; + let mut geode_robot_ore = 0; + let mut geode_robot_obsidian = 0; + // Unfortunately, this macro only takes `ident`, not `expr`, so I have to use temporary + // variables that are assigned to fields of the blueprint later + sscanf!(line, "Blueprint {}: Each ore robot costs {} ore. Each clay robot costs {} ore. Each obsidian robot costs {} ore and {} clay. Each geode robot costs {} ore and {} obsidian.", number, + ore_robot_ore, clay_robot_ore, obsidian_robot_ore, obsidian_robot_clay, geode_robot_ore, geode_robot_obsidian).unwrap(); + bp.number = number; + bp.ore_robot.ore = ore_robot_ore; + bp.clay_robot.ore = clay_robot_ore; + bp.obsidian_robot.ore = obsidian_robot_ore; + bp.obsidian_robot.clay = obsidian_robot_clay; + bp.geode_robot.ore = geode_robot_ore; + bp.geode_robot.obsidian = geode_robot_obsidian; + bp.max_ore = clay_robot_ore.max(obsidian_robot_ore).max(geode_robot_ore); + bp + }).collect() +} + +fn part1(parsed: &Parsed) -> u32 { + parsed.iter().map(|blueprint| max_geodes(Factory { ore_robot: 1, ..Default::default() }, blueprint, 24) * blueprint.number).sum() +} + +fn part2(parsed: &Parsed) -> u32 { + parsed.iter().take(3).map(|blueprint| max_geodes(Factory { ore_robot: 1, ..Default::default() }, blueprint, 32)).product() +} + +fn max_geodes(factory: Factory, bp: &Blueprint, limit: u32) -> u32 { + if factory.minute == limit { + return factory.geode; + } + if factory.can_afford(&bp.geode_robot) { + return max_geodes(Factory { geode_robot: factory.geode_robot + 1, ..factory.pass_minute() } - bp.geode_robot, bp, limit); + } + // This assumption holds for my input but not the test input. + // Not entirely fair, but good enough until I figure out a better way. + if factory.obsidian_robot < bp.geode_robot.obsidian && factory.can_afford(&bp.obsidian_robot) { + return max_geodes(Factory { obsidian_robot: factory.obsidian_robot + 1, ..factory.pass_minute() } - bp.obsidian_robot, bp, limit); + } + let mut outcomes = Vec::with_capacity(3); + if factory.ore_robot < bp.max_ore && factory.can_afford(&bp.ore_robot) { + outcomes.push(Factory { ore_robot: factory.ore_robot + 1, ..factory.pass_minute() } - bp.ore_robot); + } + if factory.clay_robot < bp.obsidian_robot.clay && factory.can_afford(&bp.clay_robot) { + outcomes.push(Factory { clay_robot: factory.clay_robot + 1, ..factory.pass_minute() } - bp.clay_robot); + } + // There seem to be steps where doing nothing is the right choice even if we could produce + // another robot according to the rules above. If I could eliminate this, the number of tested + // lines is reduce by a factor of ~1_000_000 + outcomes.push(factory.pass_minute()); + outcomes.into_iter().map(|f| max_geodes(f, bp, limit)).max().unwrap() +} + +boilerplate! { + TEST_INPUT == "\ +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.", + tests: { + part1: { TEST_INPUT => 33 }, + // part2: { TEST_INPUT => 3472 }, + }, + bench1 == 1150, + bench2 == 37367, + bench_parse: Vec::len => 30, +}