diff --git a/Cargo.lock b/Cargo.lock index cf808fa..d6500a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,9 +6,12 @@ version = 3 name = "aoc2022" version = "0.1.0" dependencies = [ + "bitvec", "itertools", "nom", "num", + "petgraph", + "rayon", ] [[package]] @@ -17,12 +20,110 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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 = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itertools" version = "0.10.5" @@ -32,12 +133,27 @@ dependencies = [ "either", ] +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + [[package]] name = "memchr" 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 = "minimal-lexical" version = "0.2.1" @@ -129,3 +245,72 @@ checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] + +[[package]] +name = "num_cpus" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[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 = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml index 8310778..0be3bff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bitvec = "1.0.1" itertools = "0.10.5" nom = "7.1.1" num = "0.4.0" +petgraph = "0.6.2" +rayon = "1.6.1" diff --git a/inputs/day16.txt b/inputs/day16.txt new file mode 100644 index 0000000..2303216 --- /dev/null +++ b/inputs/day16.txt @@ -0,0 +1,55 @@ +Valve NA has flow rate=0; tunnels lead to valves MU, PH +Valve NW has flow rate=0; tunnels lead to valves KB, MH +Valve MR has flow rate=0; tunnels lead to valves GC, FI +Valve XD has flow rate=0; tunnels lead to valves UN, CN +Valve HK has flow rate=0; tunnels lead to valves AA, IF +Valve JL has flow rate=0; tunnels lead to valves IF, WB +Valve RQ has flow rate=13; tunnels lead to valves BL, DJ +Valve AB has flow rate=0; tunnels lead to valves BO, RU +Valve PE has flow rate=0; tunnels lead to valves AZ, IF +Valve QF has flow rate=0; tunnels lead to valves TD, AZ +Valve BA has flow rate=0; tunnels lead to valves RF, GU +Valve SY has flow rate=0; tunnels lead to valves MH, MU +Valve NT has flow rate=0; tunnels lead to valves DJ, UN +Valve GU has flow rate=21; tunnels lead to valves VJ, BA, YP +Valve AZ has flow rate=12; tunnels lead to valves QF, PI, AS, PE +Valve WQ has flow rate=23; tunnels lead to valves VJ, UM, CN +Valve DR has flow rate=0; tunnels lead to valves GA, CQ +Valve UM has flow rate=0; tunnels lead to valves IE, WQ +Valve XI has flow rate=0; tunnels lead to valves IE, IF +Valve SS has flow rate=0; tunnels lead to valves CQ, MH +Valve IE has flow rate=22; tunnels lead to valves YP, UM, XI, XA +Valve BT has flow rate=24; tunnels lead to valves KB, BL, GA +Valve GA has flow rate=0; tunnels lead to valves DR, BT +Valve AR has flow rate=0; tunnels lead to valves IF, FI +Valve DJ has flow rate=0; tunnels lead to valves RQ, NT +Valve PI has flow rate=0; tunnels lead to valves FI, AZ +Valve WB has flow rate=0; tunnels lead to valves TD, JL +Valve OQ has flow rate=0; tunnels lead to valves ME, TD +Valve RU has flow rate=19; tunnel leads to valve AB +Valve IF has flow rate=7; tunnels lead to valves AR, JL, HK, PE, XI +Valve BO has flow rate=0; tunnels lead to valves ME, AB +Valve CN has flow rate=0; tunnels lead to valves WQ, XD +Valve HH has flow rate=0; tunnels lead to valves AA, FS +Valve AS has flow rate=0; tunnels lead to valves AA, AZ +Valve FS has flow rate=0; tunnels lead to valves HH, MH +Valve PQ has flow rate=0; tunnels lead to valves TD, AA +Valve AA has flow rate=0; tunnels lead to valves HH, CO, AS, HK, PQ +Valve ME has flow rate=18; tunnels lead to valves OQ, BO, PH +Valve RF has flow rate=0; tunnels lead to valves UN, BA +Valve MH has flow rate=8; tunnels lead to valves FS, NW, SS, SY +Valve YP has flow rate=0; tunnels lead to valves IE, GU +Valve FI has flow rate=11; tunnels lead to valves PI, MR, AR, CO, DI +Valve UU has flow rate=0; tunnels lead to valves CQ, MU +Valve CO has flow rate=0; tunnels lead to valves AA, FI +Valve TD has flow rate=16; tunnels lead to valves QF, GC, OQ, WB, PQ +Valve MU has flow rate=15; tunnels lead to valves SY, UU, NA +Valve BL has flow rate=0; tunnels lead to valves BT, RQ +Valve PH has flow rate=0; tunnels lead to valves ME, NA +Valve XA has flow rate=0; tunnels lead to valves IE, DI +Valve GC has flow rate=0; tunnels lead to valves TD, MR +Valve KB has flow rate=0; tunnels lead to valves BT, NW +Valve DI has flow rate=0; tunnels lead to valves XA, FI +Valve CQ has flow rate=9; tunnels lead to valves UU, DR, SS +Valve VJ has flow rate=0; tunnels lead to valves WQ, GU +Valve UN has flow rate=20; tunnels lead to valves NT, XD, RF diff --git a/src/bin/day16_1.rs b/src/bin/day16_1.rs new file mode 100644 index 0000000..8c2b5c1 --- /dev/null +++ b/src/bin/day16_1.rs @@ -0,0 +1,8 @@ +use std::fs; + +use aoc2022::day16::process_part_1; + +fn main() { + let file = fs::read_to_string("./inputs/day16.txt").unwrap(); + println!("{}", process_part_1(&file)); +} diff --git a/src/bin/day16_2.rs b/src/bin/day16_2.rs new file mode 100644 index 0000000..e930c5d --- /dev/null +++ b/src/bin/day16_2.rs @@ -0,0 +1,8 @@ +use std::fs; + +use aoc2022::day16::process_part_2; + +fn main() { + let file = fs::read_to_string("./inputs/day16.txt").unwrap(); + println!("{}", process_part_2(&file)); +} diff --git a/src/day16.rs b/src/day16.rs new file mode 100644 index 0000000..b6eede9 --- /dev/null +++ b/src/day16.rs @@ -0,0 +1,189 @@ +use std::collections::HashMap; + +use bitvec::prelude::BitArray; +use itertools::Itertools; +use nom::branch::alt; +use nom::bytes::complete::{tag, take}; +use nom::character::complete; +use nom::character::complete::newline; +use nom::multi::separated_list1; +use nom::sequence::preceded; +use nom::IResult; +use petgraph::algo::floyd_warshall; +use petgraph::prelude::{NodeIndex, UnGraph}; +use rayon::prelude::*; + +pub fn process_part_1(input: &str) -> usize { + let data = parse_input(input).unwrap().1; + let distances = floyd_warshall(&data.graph, |_| 1).unwrap(); + let targets = data.targets(); + max_released( + &data.graph, + &distances, + data.start, + 30, + &targets, + BitArray::ZERO, + ) +} + +pub fn process_part_2(input: &str) -> usize { + let data = parse_input(input).unwrap().1; + let distances = floyd_warshall(&data.graph, |_| 1).unwrap(); + let targets = data.targets(); + + // Split targets about evenly. Human takes (i) nodes, leaving (|targets| - i) nodes for the elephant + (1..=targets.len() / 2) + .into_par_iter() + .flat_map(|n| targets.iter().copied().combinations(n).collect_vec()) + .map(|human_nodes| { + let elephant_nodes = targets + .iter() + .copied() + .filter(|n| !human_nodes.contains(n)) + .collect(); + let max_human = max_released( + &data.graph, + &distances, + data.start, + 26, + &human_nodes, + BitArray::ZERO, + ); + let max_elephant = max_released( + &data.graph, + &distances, + data.start, + 26, + &elephant_nodes, + BitArray::ZERO, + ); + + max_human + max_elephant + }) + .max() + .unwrap() +} + +fn max_released( + graph: &UnGraph, + distances: &HashMap<(NodeIndex, NodeIndex), i32>, + start: NodeIndex, + remaining_time: usize, + targets: &Vec, + opened: BitArray, +) -> usize { + let mut max = 0; + // If we are out of time, or if we have opened all valves + if remaining_time == 0 || opened.count_ones() == targets.len() { + return 0; + } + + for &valve in targets.iter().filter(|&n| !opened[n.index()]) { + let distance = distances[&(start, valve)].unsigned_abs() as usize; + if remaining_time <= distance { + continue; + } + + let mut opened = opened; + opened.set(valve.index(), true); + + let next_time_remaining = remaining_time.saturating_sub(distance).saturating_sub(1); + let next_released = max_released( + graph, + distances, + valve, + next_time_remaining, + targets, + opened, + ); + let flow = *graph.node_weight(valve).unwrap() * next_time_remaining; + if flow + next_released > max { + max = flow + next_released; + } + } + + max +} + +#[derive(Debug, Clone)] +struct Data { + graph: UnGraph, + start: NodeIndex, +} + +impl Data { + fn targets(&self) -> Vec { + self.graph + .node_indices() + .filter(|&n| *self.graph.node_weight(n).unwrap() > 0) + .collect() + } +} + +fn parse_input(input: &str) -> IResult<&str, Data> { + let (input, parsed_valves) = separated_list1(newline, parse_line)(input)?; + let mut graph = UnGraph::new_undirected(); + let mut valves = HashMap::new(); + let mut valves_by_index = HashMap::new(); + + // generate graph nodes + for valve in &parsed_valves { + let node = graph.add_node(valve.1); + valves.insert(valve.0, node); + valves_by_index.insert(node, valve.0); + } + // generate graph edges + for valve in &parsed_valves { + let valve_node = valves.get(valve.0).unwrap(); + + for &neighbor in &valve.2 { + let neighbor_node = valves.get(neighbor).unwrap(); + graph.add_edge(*valve_node, *neighbor_node, 1); + } + } + + let start = *valves.get("AA").unwrap(); + + Ok((input, Data { graph, start })) +} + +fn parse_line(input: &str) -> IResult<&str, (&str, usize, Vec<&str>)> { + let (input, valve_name) = preceded(tag("Valve "), take(2usize))(input)?; + let (input, flow) = preceded(tag(" has flow rate="), complete::u32)(input)?; + let (input, neighbors) = preceded( + alt(( + tag("; tunnels lead to valves "), + tag("; tunnel leads to valve "), + )), + separated_list1(tag(", "), take(2usize)), + )(input)?; + + Ok((input, (valve_name, flow as usize, neighbors))) +} + +#[cfg(test)] +mod tests { + use super::*; + + const INPUT: &str = "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"; + + #[test] + fn day1() { + assert_eq!(process_part_1(INPUT), 1651); + } + + #[test] + fn day2() { + assert_eq!(process_part_2(INPUT), 1707); + } +} diff --git a/src/lib.rs b/src/lib.rs index 826ad25..2882a36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,3 +16,4 @@ pub mod day12; pub mod day13; pub mod day14; pub mod day15; +pub mod day16;