diff --git a/Cargo.lock b/Cargo.lock index 41e2869..aaf5bd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,49 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2875caafcad10de47ec09ead140f27a276a25b0b2f553cec9b2152194b96bf5d" +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + [[package]] name = "darling" version = "0.13.4" @@ -353,6 +396,11 @@ dependencies = [ [[package]] name = "day19" version = "0.1.0" +dependencies = [ + "lazy_static", + "rayon", + "regex", +] [[package]] name = "day20" @@ -384,6 +432,12 @@ dependencies = [ name = "day25" version = "0.1.0" +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "errno" version = "0.2.8" @@ -632,6 +686,25 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "numtoa" version = "0.1.0" @@ -746,6 +819,28 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -804,6 +899,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.151" diff --git a/day19/Cargo.toml b/day19/Cargo.toml index 8d7ba62..a70ccc4 100644 --- a/day19/Cargo.toml +++ b/day19/Cargo.toml @@ -8,3 +8,6 @@ build = true app = true [dependencies] +lazy_static = "1.4.0" +rayon = "1.6.1" +regex = "1.7.0" diff --git a/day19/input b/day19/input new file mode 100644 index 0000000..4d845a2 --- /dev/null +++ b/day19/input @@ -0,0 +1,30 @@ +Blueprint 1: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 2 ore and 20 obsidian. +Blueprint 2: 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 3: 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 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 15 clay. Each geode robot costs 2 ore and 13 obsidian. +Blueprint 5: 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 6: 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 7: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 12 clay. Each geode robot costs 4 ore and 19 obsidian. +Blueprint 8: 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 2 ore and 13 obsidian. +Blueprint 9: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 9 clay. Each geode robot costs 2 ore and 9 obsidian. +Blueprint 10: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 12 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 11: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 10 clay. Each geode robot costs 2 ore and 13 obsidian. +Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 6 clay. Each geode robot costs 3 ore and 16 obsidian. +Blueprint 13: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 3 ore and 19 obsidian. +Blueprint 14: 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 15: 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 16: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 4 ore and 10 obsidian. +Blueprint 17: 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 18: 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 19: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 13 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 20: 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 21: 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 15 obsidian. +Blueprint 22: 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 2 ore and 15 obsidian. +Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 5 clay. Each geode robot costs 2 ore and 10 obsidian. +Blueprint 24: 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 25: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 10 obsidian. +Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 6 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 5 clay. Each geode robot costs 3 ore and 15 obsidian. +Blueprint 28: 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 29: 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 30: 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. diff --git a/day19/input.test b/day19/input.test new file mode 100644 index 0000000..5212cde --- /dev/null +++ b/day19/input.test @@ -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. \ No newline at end of file diff --git a/day19/src/main.rs b/day19/src/main.rs index e7a11a9..d01efc5 100644 --- a/day19/src/main.rs +++ b/day19/src/main.rs @@ -1,3 +1,283 @@ -fn main() { - println!("Hello, world!"); +#![feature(const_trait_impl)] + +use std::{ + collections::{hash_map::Entry, HashMap}, + fs::File, + io::{BufRead, BufReader}, + ops::Add, +}; + +use lazy_static::lazy_static; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use regex::Regex; + +type Ore = usize; +type Clay = usize; +type Obsidian = usize; +type Geode = usize; + +lazy_static! { + static ref BLUEPRINT_RE: Regex = Regex::new(r"Blueprint (\d+): Each ore robot costs (\d+) ore. Each clay robot costs (\d+) ore. Each obsidian robot costs (\d+) ore and (\d+) clay. Each geode robot costs (\d+) ore and (\d+) obsidian.").unwrap(); +} + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +struct ResourceState { + ore: Ore, + clay: Clay, + obsidian: Obsidian, + geodes: Geode, +} + +impl ResourceState { + fn maximize(&mut self, other: Self) { + self.ore = self.ore.max(other.ore); + self.clay = self.clay.max(other.clay); + self.obsidian = self.obsidian.max(other.obsidian); + self.geodes = self.geodes.max(other.geodes); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct RobotState { + ore_robots: usize, + clay_robots: usize, + obisidian_robots: usize, + geode_robots: usize, +} + +impl RobotState { + fn successors( + self, + possible_resources: ResourceState, + blueprint: &Blueprint, + ) -> Vec<(Self, ResourceState)> { + let mut ret = vec![]; + let collected = possible_resources + self.collect_ores_for_one_minute(); + if possible_resources.ore >= blueprint.ore_robot_cost { + ret.push(( + Self { + ore_robots: self.ore_robots + 1, + ..self + }, + ResourceState { + ore: collected.ore - blueprint.ore_robot_cost, + ..collected + }, + )); + } + if possible_resources.ore >= blueprint.clay_robot_cost { + ret.push(( + Self { + clay_robots: self.clay_robots + 1, + ..self + }, + ResourceState { + ore: collected.ore - blueprint.clay_robot_cost, + ..collected + }, + )); + } + if possible_resources.ore >= blueprint.obsidian_robot_cost.0 + && possible_resources.clay >= blueprint.obsidian_robot_cost.1 + { + ret.push(( + Self { + obisidian_robots: self.obisidian_robots + 1, + ..self + }, + ResourceState { + ore: collected.ore - blueprint.obsidian_robot_cost.0, + clay: collected.clay - blueprint.obsidian_robot_cost.1, + ..collected + }, + )); + } + if possible_resources.ore >= blueprint.geode_robot_cost.0 + && possible_resources.obsidian >= blueprint.geode_robot_cost.1 + { + ret.push(( + Self { + geode_robots: self.geode_robots + 1, + ..self + }, + ResourceState { + ore: collected.ore - blueprint.geode_robot_cost.0, + obsidian: collected.obsidian - blueprint.geode_robot_cost.1, + ..collected + }, + )); + } + // let we_can_afford_every_robot_or_will_never = self.ore >= blueprint.ore_robot_cost + // && self.ore >= blueprint.clay_robot_cost + // && self.ore >= blueprint.obsidian_robot_cost.0 + // && (self.clay >= blueprint.obsidian_robot_cost.1 || self.clay_robots == 0) + // && self.ore >= blueprint.geode_robot_cost.0 + // && (self.obsidian >= blueprint.geode_robot_cost.1 || self.obisidian_robots == 0); + // // Do nothing, just collect ores + // if !we_can_afford_every_robot_or_will_never { + ret.push((self, collected)); + // } + ret + } + + fn collect_ores_for_one_minute(&self) -> ResourceState { + ResourceState { + ore: self.ore_robots, + clay: self.clay_robots, + obsidian: self.obisidian_robots, + geodes: self.geode_robots, + } + } +} + +#[derive(Debug)] +struct Blueprint { + id: usize, + ore_robot_cost: Ore, + clay_robot_cost: Ore, + obsidian_robot_cost: (Ore, Clay), + geode_robot_cost: (Ore, Obsidian), +} + +impl Blueprint { + fn parse_line>(line: S) -> Self { + let matches = BLUEPRINT_RE.captures(line.as_ref()).unwrap(); + let match_nr = |nr| matches.get(nr).unwrap().as_str().parse().unwrap(); + Self { + id: match_nr(1), + ore_robot_cost: match_nr(2), + clay_robot_cost: match_nr(3), + obsidian_robot_cost: (match_nr(4), match_nr(5)), + geode_robot_cost: (match_nr(6), match_nr(7)), + } + } + + fn calculate_max_geode_openable(&self) -> usize { + const MAX_TIME: usize = 24; + let mut states: HashMap = HashMap::new(); + states.insert(RobotState::default(), ResourceState::default()); + for minute in 1..=MAX_TIME { + states = states + .into_iter() + .flat_map(|(state, res)| state.successors(res, self)) + .fold(HashMap::new(), |mut map, (state, res)| { + match map.entry(state) { + Entry::Occupied(mut occ) => occ.get_mut().maximize(res), + Entry::Vacant(vac) => { + vac.insert(res); + } + }; + map + }); + // if minute == 24 { + eprintln!("{minute} {}", states.len()); + if minute < 6 { + states.iter().for_each(|x| eprintln!("{x:#?}")); + } + // } + } + states + .into_values() + .map(|s| s.geodes) + .max() + .unwrap_or_default() + } +} + +#[derive(Debug)] +struct Blueprints(Vec); + +impl Blueprints { + fn parse_input(reader: R) -> Blueprints { + let inner = reader + .lines() + .map(Result::unwrap) + .map(Blueprint::parse_line) + .collect(); + Self(inner) + } + + fn total_score(&self) -> usize { + self.0 + .par_iter() + .map(|bp| bp.calculate_max_geode_openable() * bp.id) + .sum() + } +} + +fn solve_first_puzzle(reader: R) -> usize { + Blueprints::parse_input(reader).total_score() +} + +fn solve_second_puzzle(reader: R) -> usize { + todo!() +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let file = BufReader::new(File::open(file).expect("Opening file")); + match solve { + SolvePuzzle::First => println!("{}", solve_first_puzzle(file)), + SolvePuzzle::Second => println!("{}", solve_second_puzzle(file)), + }; +} + +impl const Default for RobotState { + fn default() -> Self { + Self { + // Just one ore robot + ore_robots: 1, + clay_robots: 0, + obisidian_robots: 0, + geode_robots: 0, + } + } +} + +impl const Add for ResourceState { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self { + ore: self.ore + rhs.ore, + clay: self.clay + rhs.clay, + obsidian: self.obsidian + rhs.obsidian, + geodes: self.geodes + rhs.geodes, + } + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[test] + fn example_on_first() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_first_puzzle(reader), 33); + } + + #[test] + fn example_on_second() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_second_puzzle(reader), 54); + } }