From cf9b8f0321fe12edb41909417da3de5a5e75711a Mon Sep 17 00:00:00 2001 From: Lars Martens Date: Sun, 12 Dec 2021 12:09:53 +0100 Subject: [PATCH] Day 12 --- input/12.txt | 21 ++++++ src/bin/12.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 input/12.txt create mode 100644 src/bin/12.rs diff --git a/input/12.txt b/input/12.txt new file mode 100644 index 0000000..5a75dd8 --- /dev/null +++ b/input/12.txt @@ -0,0 +1,21 @@ +mx-IQ +mx-HO +xq-start +start-HO +IE-qc +HO-end +oz-xq +HO-ni +ni-oz +ni-MU +sa-IE +IE-ni +end-sa +oz-sa +MU-start +MU-sa +oz-IE +HO-xq +MU-xq +IE-end +MU-mx diff --git a/src/bin/12.rs b/src/bin/12.rs new file mode 100644 index 0000000..c66af7d --- /dev/null +++ b/src/bin/12.rs @@ -0,0 +1,193 @@ +use std::collections::HashMap; + +fn main() { + let puzzle = Network::from_str(include_str!("../../input/12.txt")); + println!("First solution: {}", puzzle.solve(Path::new(), false)); + println!("Second solution: {}", puzzle.solve(Path::new(), true)); +} + +type Cave = &'static str; + +struct Network { + connections: HashMap>, +} + +#[derive(Clone, Debug)] +struct Path { + caves: Vec, +} + +impl Network { + // Recursively count the number of paths. + fn solve(&self, current: Path, part2: bool) -> usize { + if current.end() == "end" { + return 1; + } + + let mut n = 0; + + for &next in &self.connections[current.end()] { + let can_visit = if part2 { + current.can_visit_plus(next) + } else { + current.can_visit(next) + }; + + if can_visit && next != "start" { + let mut next_path = current.clone(); + next_path.visit(next); + n += self.solve(next_path, part2); + } + } + n + } + + fn from_str(s: &'static str) -> Self { + let mut connections: HashMap> = HashMap::new(); + for (from, to) in s.lines().filter_map(|l| l.trim().split_once('-')) { + connections.entry(from).or_default().push(to); + connections.entry(to).or_default().push(from); + } + + Self { connections } + } +} + +impl Path { + fn new() -> Self { + Self { + caves: vec!["start"], + } + } + fn can_visit(&self, c: Cave) -> bool { + if c.to_lowercase() == c { + // Small caves may be visitied at most once + !self.caves.iter().any(|&cc| c == cc) + } else { + // Big caves can be visited any number of times + true + } + } + // Allows a single small cave to be visited twice. + fn can_visit_plus(&self, c: Cave) -> bool { + if c.to_lowercase() == c { + let already_visited = self.caves.iter().any(|&cc| c == cc); + if already_visited { + // We have already been to this small cave. Find out if we visited any single + // cave twice already. + let mut only_small_caves: Vec = self + .caves + .clone() + .into_iter() + .filter(|&s| s.to_lowercase() == s) + .collect(); + only_small_caves.sort_unstable(); + let a = only_small_caves.len(); + only_small_caves.dedup(); + let b = only_small_caves.len(); + a == b + } else { + true + } + } else { + true + } + } + fn visit(&mut self, c: Cave) { + self.caves.push(c); + } + fn end(&self) -> Cave { + self.caves.last().unwrap() + } +} + +#[cfg(test)] +mod test { + use crate::{Network, Path}; + + #[test] + fn input10() { + assert_eq!( + Network::from_str( + r"start-A + start-b + A-c + A-b + b-d + A-end + b-end + " + ) + .solve(Path::new(), false), + 10 + ); + } + + #[test] + fn input10plus() { + assert_eq!( + Network::from_str( + r"start-A + start-b + A-c + A-b + b-d + A-end + b-end + " + ) + .solve(Path::new(), true), + 36 + ); + } + + #[test] + fn input19() { + assert_eq!( + Network::from_str( + r"dc-end + HN-start + start-kj + dc-start + dc-HN + LN-dc + HN-end + kj-sa + kj-HN + kj-dc + " + ) + .solve(Path::new(), false), + 19 + ); + } + + #[test] + fn input226() { + assert_eq!( + Network::from_str( + r"fs-end + he-DX + fs-he + start-DX + pj-DX + end-zg + zg-sl + zg-pj + pj-he + RW-he + fs-DX + pj-RW + zg-RW + start-pj + he-WI + zg-he + pj-fs + start-RW + " + ) + .solve(Path::new(), false), + 226 + ); + } +}