diff --git a/.aoc-cache/16.txt b/.aoc-cache/16.txt new file mode 100644 index 0000000..19418dd --- /dev/null +++ b/.aoc-cache/16.txt @@ -0,0 +1,52 @@ +Valve JZ has flow rate=0; tunnels lead to valves IR, LY +Valve KD has flow rate=0; tunnels lead to valves NJ, ZS +Valve VW has flow rate=0; tunnels lead to valves IT, VH +Valve HS has flow rate=0; tunnels lead to valves OC, PN +Valve EU has flow rate=19; tunnel leads to valve GQ +Valve XF has flow rate=0; tunnels lead to valves WL, QD +Valve DD has flow rate=8; tunnels lead to valves GQ, YY, JV, SK +Valve TA has flow rate=0; tunnels lead to valves NJ, VJ +Valve IR has flow rate=9; tunnels lead to valves JZ, WI, VJ, GC, WG +Valve SS has flow rate=17; tunnels lead to valves SI, IZ, RK, WI +Valve SG has flow rate=0; tunnels lead to valves NV, NJ +Valve IT has flow rate=0; tunnels lead to valves LL, VW +Valve CP has flow rate=24; tunnels lead to valves HN, ZK, EJ +Valve SK has flow rate=0; tunnels lead to valves LL, DD +Valve IS has flow rate=0; tunnels lead to valves AA, LL +Valve HN has flow rate=0; tunnels lead to valves FF, CP +Valve VH has flow rate=10; tunnels lead to valves QO, VW, RV, PN +Valve JV has flow rate=0; tunnels lead to valves DD, RK +Valve ZS has flow rate=0; tunnels lead to valves KD, LL +Valve UC has flow rate=25; tunnels lead to valves JD, IV +Valve WI has flow rate=0; tunnels lead to valves SS, IR +Valve UR has flow rate=0; tunnels lead to valves QD, LY +Valve GC has flow rate=0; tunnels lead to valves AA, IR +Valve YY has flow rate=0; tunnels lead to valves DD, AA +Valve IV has flow rate=0; tunnels lead to valves ZK, UC +Valve BM has flow rate=0; tunnels lead to valves SA, WL +Valve JD has flow rate=0; tunnels lead to valves IZ, UC +Valve WL has flow rate=12; tunnels lead to valves EF, BM, EJ, XF +Valve AA has flow rate=0; tunnels lead to valves NV, YY, GC, IS, QO +Valve WG has flow rate=0; tunnels lead to valves LL, IR +Valve GQ has flow rate=0; tunnels lead to valves EU, DD +Valve SI has flow rate=0; tunnels lead to valves SS, NJ +Valve KH has flow rate=13; tunnels lead to valves SA, ON +Valve PC has flow rate=22; tunnel leads to valve ON +Valve QD has flow rate=14; tunnels lead to valves XF, UR +Valve IZ has flow rate=0; tunnels lead to valves SS, JD +Valve QO has flow rate=0; tunnels lead to valves AA, VH +Valve SA has flow rate=0; tunnels lead to valves BM, KH +Valve NV has flow rate=0; tunnels lead to valves AA, SG +Valve ZK has flow rate=0; tunnels lead to valves CP, IV +Valve ON has flow rate=0; tunnels lead to valves PC, KH +Valve PN has flow rate=0; tunnels lead to valves HS, VH +Valve RV has flow rate=0; tunnels lead to valves NJ, VH +Valve RK has flow rate=0; tunnels lead to valves SS, JV +Valve OC has flow rate=18; tunnel leads to valve HS +Valve EF has flow rate=0; tunnels lead to valves LY, WL +Valve VJ has flow rate=0; tunnels lead to valves TA, IR +Valve LL has flow rate=5; tunnels lead to valves ZS, IT, SK, IS, WG +Valve FF has flow rate=0; tunnels lead to valves HN, LY +Valve LY has flow rate=21; tunnels lead to valves EF, FF, UR, JZ +Valve EJ has flow rate=0; tunnels lead to valves WL, CP +Valve NJ has flow rate=6; tunnels lead to valves RV, KD, SG, SI, TA diff --git a/src/bin/day_16.rs b/src/bin/day_16.rs new file mode 100644 index 0000000..52d2a15 --- /dev/null +++ b/src/bin/day_16.rs @@ -0,0 +1,264 @@ +use std::collections::{HashMap, HashSet}; + +use aoc_2022::prelude::*; + +use lru_cache::LruCache; +use regex::Regex; + +type Input = HashMap; + +#[derive(Debug)] +struct Valve { + flow_rate: usize, + tunnels: Vec, +} + +fn parse(s: &str) -> Result { + let re = Regex::new("Valve ([A-Z]{2}) has flow rate=([0-9]+)")?; + + let mut valves = HashMap::new(); + + for line in s.lines() { + let (a, b) = line.split_once("; ").unwrap(); + let captures = re.captures(a).unwrap(); + let name = captures.get(1).unwrap().as_str().to_owned(); + let flow_rate = captures.get(2).unwrap().as_str().parse()?; + + let tunnels = b + .trim_start_matches("tunnel leads to valve") + .trim_start_matches("tunnels lead to valves") + .trim_start() + .split(", ") + .map(|s| s.to_owned()) + .collect(); + + valves.insert(name, Valve { flow_rate, tunnels }); + } + + Ok(valves) +} + +#[aoc(day = 16, parse = parse, test_cases = ["day_16.txt"])] +fn day_16(input: Input) -> Result<()> { + // Part 1 + let (_path, greatest) = explore( + &input, + 29, + "AA", + &HashSet::new(), + "AA".to_owned(), + &mut HashMap::new(), + ); + println!("Part one: {greatest}"); + // println!("{path}"); + + // Part 2 + let greatest = explore_with_elephant( + &input, + 25, + "AA", + "AA", + &HashSet::new(), + &mut LruCache::new(4_000_000), + ); + println!("Part two: {greatest}"); + Ok(()) +} + +#[derive(Clone)] +enum Action<'a> { + Open, + MoveTo(&'a str), + DoLiterallyNothingForAMinute, +} + +fn sum_flow(input: &Input, open: &HashSet) -> usize { + open.iter().map(|n| input[n].flow_rate).sum() +} + +fn encode_open(open: &HashSet) -> String { + let mut result = String::new(); + let mut open_vec = open.iter().collect::>(); + open_vec.sort(); + for n in open_vec { + result.push_str(n); + } + result +} + +fn explore( + input: &Input, + depth: usize, + node: &str, + open: &HashSet, + path: String, + cache: &mut HashMap<(usize, String, String), (String, usize)>, +) -> (String, usize) { + let pressure_this_round = sum_flow(input, &open); + + let cache_key = (depth, node.to_owned(), encode_open(open)); + if let Some(result) = cache.get(&cache_key) { + return (result.0.clone(), pressure_this_round + result.1); + } + + if depth == 0 { + return (path, pressure_this_round); + } + + let current = &input[node]; + let mut actions = current + .tunnels + .iter() + .map(|tunnel| Action::MoveTo(&*tunnel)) + .collect::>(); + + actions.push(Action::DoLiterallyNothingForAMinute); + + if current.flow_rate != 0 && !open.contains(node) { + actions.insert(0, Action::Open); + } + + let result = actions + .iter() + .map(|action| match action { + Action::Open => { + let mut open = open.clone(); + open.insert(node.to_owned()); + explore( + input, + depth - 1, + node, + &open, + format!("{path}\n*open* ({pressure_this_round})"), + cache, + ) + } + Action::MoveTo(n) => explore( + input, + depth - 1, + *n, + open, + format!("{path}\nmove to {n} ({pressure_this_round})"), + cache, + ), + Action::DoLiterallyNothingForAMinute => explore( + input, + depth - 1, + node, + open, + format!("{path}\n*wait* ({pressure_this_round})"), + cache, + ), + }) + .max_by_key(|r| r.1) + .unwrap(); + + cache.insert(cache_key, result.clone()); + + (result.0, result.1 + pressure_this_round) +} + +fn explore_with_elephant( + input: &Input, + depth: usize, + my_node: &str, + elephant_node: &str, + open: &HashSet, + cache: &mut LruCache<(usize, String, String, String), usize>, +) -> usize { + let pressure_this_round = sum_flow(input, &open); + + let cache_key = ( + depth, + my_node.to_owned(), + elephant_node.to_owned(), + encode_open(open), + ); + if let Some(result) = cache.get_mut(&cache_key) { + return pressure_this_round + *result; + } + + if depth == 0 { + return pressure_this_round; + } + + let my_actions = { + let current = &input[my_node]; + let mut my_actions = current + .tunnels + .iter() + .map(|tunnel| Action::MoveTo(&*tunnel)) + .collect::>(); + + my_actions.push(Action::DoLiterallyNothingForAMinute); + + if current.flow_rate != 0 && !open.contains(my_node) { + my_actions.insert(0, Action::Open); + } + + my_actions + }; + + let elephant_actions = { + let current = &input[elephant_node]; + let mut elephant_actions = current + .tunnels + .iter() + .map(|tunnel| Action::MoveTo(&*tunnel)) + .collect::>(); + + elephant_actions.push(Action::DoLiterallyNothingForAMinute); + + if current.flow_rate != 0 && !open.contains(elephant_node) { + elephant_actions.insert(0, Action::Open); + } + + elephant_actions + }; + + let paired_actions = my_actions + .into_iter() + .flat_map(|action| { + elephant_actions + .iter() + .map(Clone::clone) + .map(move |a| (action.clone(), a)) + }) + .filter(|(a, b)| match (a, b) { + (Action::Open, Action::Open) => false, + _ => true, + }); + + let result = paired_actions + .map(|(my_action, elephant_action)| { + let (open, my_node) = match my_action { + Action::Open => { + let mut open = open.clone(); + open.insert(my_node.to_owned()); + (open, my_node.to_owned()) + } + Action::MoveTo(node) => (open.clone(), node.to_owned()), + Action::DoLiterallyNothingForAMinute => (open.clone(), my_node.to_owned()), + }; + + let (open, elephant_node) = match elephant_action { + Action::Open => { + let mut open = open.clone(); + open.insert(elephant_node.to_owned()); + (open, elephant_node.to_owned()) + } + Action::MoveTo(node) => (open.clone(), node.to_owned()), + Action::DoLiterallyNothingForAMinute => (open.clone(), elephant_node.to_owned()), + }; + + explore_with_elephant(input, depth - 1, &my_node, &elephant_node, &open, cache) + }) + .max() + .unwrap(); + + if depth < 10 || depth > 20 { + cache.insert(cache_key, result); + } + + result + pressure_this_round +} diff --git a/test_cases/day_16.txt b/test_cases/day_16.txt new file mode 100644 index 0000000..85fa5b0 --- /dev/null +++ b/test_cases/day_16.txt @@ -0,0 +1,10 @@ +Valve AA has flow rate=0; tunnels lead to valves DD, II, BB +Valve BB has flow rate=13; tunnels lead to valves CC, AA +Valve CC has flow rate=2; tunnels lead to valves DD, BB +Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE +Valve EE has flow rate=3; tunnels lead to valves FF, DD +Valve FF has flow rate=0; tunnels lead to valves EE, GG +Valve GG has flow rate=0; tunnels lead to valves FF, HH +Valve HH has flow rate=22; tunnel leads to valve GG +Valve II has flow rate=0; tunnels lead to valves AA, JJ +Valve JJ has flow rate=21; tunnel leads to valve II \ No newline at end of file