diff --git a/aoc2021/input/day12.txt b/aoc2021/input/day12.txt new file mode 100644 index 0000000..731cd4b --- /dev/null +++ b/aoc2021/input/day12.txt @@ -0,0 +1,22 @@ +GC-zi +end-zv +lk-ca +lk-zi +GC-ky +zi-ca +end-FU +iv-FU +lk-iv +lk-FU +GC-end +ca-zv +lk-GC +GC-zv +start-iv +zv-QQ +ca-GC +ca-FU +iv-ca +start-lk +zv-FU +start-zi diff --git a/aoc2021/input/day12_provided1.txt b/aoc2021/input/day12_provided1.txt new file mode 100644 index 0000000..6fd8c41 --- /dev/null +++ b/aoc2021/input/day12_provided1.txt @@ -0,0 +1,7 @@ +start-A +start-b +A-c +A-b +b-d +A-end +b-end diff --git a/aoc2021/input/day12_provided2.txt b/aoc2021/input/day12_provided2.txt new file mode 100644 index 0000000..62cc714 --- /dev/null +++ b/aoc2021/input/day12_provided2.txt @@ -0,0 +1,10 @@ +dc-end +HN-start +start-kj +dc-start +dc-HN +LN-dc +HN-end +kj-sa +kj-HN +kj-dc diff --git a/aoc2021/input/day12_provided3.txt b/aoc2021/input/day12_provided3.txt new file mode 100644 index 0000000..65f3833 --- /dev/null +++ b/aoc2021/input/day12_provided3.txt @@ -0,0 +1,18 @@ +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 diff --git a/aoc2021/src/day12.rs b/aoc2021/src/day12.rs new file mode 100644 index 0000000..c92b979 --- /dev/null +++ b/aoc2021/src/day12.rs @@ -0,0 +1,140 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; + +use anyhow::{Context, Result}; + +const INPUT: &str = include_str!("../input/day12.txt"); + +pub fn run() -> Result { + let mut res = String::with_capacity(128); + + writeln!(res, "part 1: {}", part1(INPUT)?)?; + + Ok(res) +} + +fn part1(input: &str) -> Result { + let cave_map: CaveMap = input.try_into()?; + + cave_map.count_paths() +} + +#[derive(Clone, Copy)] +struct Cave<'a> { + name: &'a str, + small: bool, +} + +impl<'a> Cave<'a> { + fn is_end(&self) -> bool { + self.name == "end" + } + + fn is_start(&self) -> bool { + self.name == "start" + } + + fn is_small(&self) -> bool { + self.small + } +} + +impl<'a> From<&'a str> for Cave<'a> { + fn from(s: &'a str) -> Self { + Cave { + name: s, + small: s.chars().all(char::is_lowercase), + } + } +} + +impl<'a> std::hash::Hash for Cave<'a> { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl<'a> std::cmp::PartialEq for Cave<'a> { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} +impl<'a> std::cmp::Eq for Cave<'a> {} + +struct CaveMap<'a> { + connections: HashMap, Vec>>, +} + +impl<'a> CaveMap<'a> { + fn count_paths(&self) -> Result { + let start = *self + .connections + .keys() + .find(|cave| cave.is_start()) + .context("couldn't find starting cave")?; + Ok(self.count_paths_rec(start, HashSet::new())) + } + + fn count_paths_rec(&self, from: Cave<'a>, mut small_seen: HashSet>) -> usize { + if from.is_end() { + return 1; + } + + if from.is_small() { + if small_seen.contains(&from) { + return 0; + } + small_seen.insert(from); + } + + let mut paths = 0; + for dst in &self.connections[&from] { + paths += self.count_paths_rec(*dst, small_seen.clone()); + } + + paths + } +} + +impl<'a> TryFrom<&'a str> for CaveMap<'a> { + type Error = anyhow::Error; + + fn try_from(s: &'a str) -> Result { + let mut map = HashMap::new(); + + for line in s.lines().map(str::trim) { + let (src, dst) = line + .split_once('-') + .context("couldn't parse cave connection")?; + map.entry(src.into()) + .or_insert_with(Vec::new) + .push(dst.into()); + map.entry(dst.into()) + .or_insert_with(Vec::new) + .push(src.into()); + } + + Ok(CaveMap { connections: map }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const PROVIDED1: &str = include_str!("../input/day12_provided1.txt"); + const PROVIDED2: &str = include_str!("../input/day12_provided2.txt"); + const PROVIDED3: &str = include_str!("../input/day12_provided3.txt"); + + #[test] + fn part1_provided() { + assert_eq!(part1(PROVIDED1).unwrap(), 10); + assert_eq!(part1(PROVIDED2).unwrap(), 19); + assert_eq!(part1(PROVIDED3).unwrap(), 226); + } + + #[test] + fn part1_real() { + assert_eq!(part1(INPUT).unwrap(), 5252); + } +} diff --git a/aoc2021/src/lib.rs b/aoc2021/src/lib.rs index 5d8eeba..cc37626 100644 --- a/aoc2021/src/lib.rs +++ b/aoc2021/src/lib.rs @@ -11,4 +11,5 @@ pub mod day08; pub mod day09; pub mod day10; pub mod day11; +pub mod day12; pub mod day13; diff --git a/aoc2021/src/main.rs b/aoc2021/src/main.rs index c21a96f..1fa15e3 100644 --- a/aoc2021/src/main.rs +++ b/aoc2021/src/main.rs @@ -13,6 +13,7 @@ use aoc2021::day08; use aoc2021::day09; use aoc2021::day10; use aoc2021::day11; +use aoc2021::day12; use aoc2021::day13; fn main() -> Result<()> { @@ -28,6 +29,7 @@ fn main() -> Result<()> { day09::run, day10::run, day11::run, + day12::run, day13::run, ];