From d73485111d255467583a924d48b1358ecbd45330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Sun, 12 Dec 2021 13:00:18 +0100 Subject: [PATCH] implement day 12 --- src/day_12/mod.rs | 63 +++++++++++++++++ src/day_12/model.rs | 156 ++++++++++++++++++++++++++++++++++++++++++ src/day_12/parsing.rs | 72 +++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 292 insertions(+) create mode 100644 src/day_12/mod.rs create mode 100644 src/day_12/model.rs create mode 100644 src/day_12/parsing.rs diff --git a/src/day_12/mod.rs b/src/day_12/mod.rs new file mode 100644 index 0000000..e6d2850 --- /dev/null +++ b/src/day_12/mod.rs @@ -0,0 +1,63 @@ +mod model; +mod parsing; + +use aoc_runner_derive::{aoc, aoc_generator}; +pub use model::{Cave, CaveSystem}; +pub use parsing::parse_cave_system; +use yap::IntoTokens; + +#[aoc_generator(day12)] +pub fn parse_input(input: &str) -> CaveSystem { + parse_cave_system(&mut input.into_tokens()).unwrap() +} + +#[aoc(day12, part1)] +pub fn part1(input: &CaveSystem) -> usize { + input.list_paths(false).len() +} + +#[aoc(day12, part2)] +pub fn part2(input: &CaveSystem) -> usize { + input.list_paths(true).len() +} + +#[cfg(test)] +mod test { + const EXAMPLE_INPUT: &str = "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"; + const RESULT_PART_1: usize = 226; + const RESULT_PART_2: usize = 3509; + + #[test] + fn part1_example() { + let result = super::part1(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_1); + } + + #[test] + fn part2_example() { + let result = super::part2(&super::parse_input(EXAMPLE_INPUT)); + assert_eq!(result, RESULT_PART_2); + } + + #[test] + fn parse_example() { + let _example = super::parse_input(EXAMPLE_INPUT); + } +} diff --git a/src/day_12/model.rs b/src/day_12/model.rs new file mode 100644 index 0000000..228099e --- /dev/null +++ b/src/day_12/model.rs @@ -0,0 +1,156 @@ +use std::collections::{HashMap, HashSet}; + +pub struct CaveSystem { + pub adjacencies: HashMap>, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub enum Cave { + Big(String), + Small(String), + Start, + End, +} + +impl ToString for Cave { + fn to_string(&self) -> String { + match self { + Cave::Big(cave) => cave.clone(), + Cave::Small(cave) => cave.clone(), + Cave::Start => String::from("start"), + Cave::End => String::from("end"), + } + } +} + +impl CaveSystem { + pub fn list_paths(&self, may_visit_twice: bool) -> Vec> { + self.recurse_path(Cave::Start, vec![Cave::Start], may_visit_twice) + } + + fn recurse_path( + &self, + current: Cave, + path: Vec, + may_visit_twice: bool, + ) -> Vec> { + let mut ret_val = Vec::new(); + for cave in self.adjacencies.get(¤t).unwrap_or(&HashSet::new()) { + match cave { + Cave::Big(_) => { + let mut path = path.clone(); + path.push(cave.clone()); + ret_val.append(&mut self.recurse_path(cave.clone(), path, may_visit_twice)); + } + Cave::Small(_) => { + let mut may_visit_twice = may_visit_twice; + if path.contains(cave) { + if may_visit_twice { + may_visit_twice = false; + } else { + continue; + } + }; + let mut path = path.clone(); + path.push(cave.clone()); + ret_val.append(&mut self.recurse_path(cave.clone(), path, may_visit_twice)); + } + Cave::Start => continue, + Cave::End => { + let mut path = path.clone(); + path.push(cave.clone()); + ret_val.push(path); + } + } + } + ret_val + } + + pub fn new(edges: Vec<[Cave; 2]>) -> Result { + let mut adjacencies: HashMap> = Default::default(); + for edge in edges { + adjacencies + .entry(edge[0].clone()) + .or_default() + .insert(edge[1].clone()); + adjacencies + .entry(edge[1].clone()) + .or_default() + .insert(edge[0].clone()); + } + if !adjacencies.contains_key(&Cave::Start) { + Err("Cave system does not contain start cave") + } else if !adjacencies.contains_key(&Cave::End) { + Err("Cave system does not contain end cave") + } else { + Ok(CaveSystem { adjacencies }) + } + } +} + +#[cfg(test)] +mod test { + use std::collections::HashSet; + + use itertools::Itertools; + + #[test] + fn small_example_part_2() { + let input: super::CaveSystem = super::super::parse_input( + "start-A +start-b +A-c +A-b +b-d +A-end +b-end", + ); + let paths: HashSet = input + .list_paths(true) + .iter() + .map(|path| path.iter().map(|cave| cave.to_string()).join(",")) + .collect(); + + let expected_paths: HashSet = "start,A,b,A,b,A,c,A,end +start,A,b,A,b,A,end +start,A,b,A,b,end +start,A,b,A,c,A,b,A,end +start,A,b,A,c,A,b,end +start,A,b,A,c,A,c,A,end +start,A,b,A,c,A,end +start,A,b,A,end +start,A,b,d,b,A,c,A,end +start,A,b,d,b,A,end +start,A,b,d,b,end +start,A,b,end +start,A,c,A,b,A,b,A,end +start,A,c,A,b,A,b,end +start,A,c,A,b,A,c,A,end +start,A,c,A,b,A,end +start,A,c,A,b,d,b,A,end +start,A,c,A,b,d,b,end +start,A,c,A,b,end +start,A,c,A,c,A,b,A,end +start,A,c,A,c,A,b,end +start,A,c,A,c,A,end +start,A,c,A,end +start,A,end +start,b,A,b,A,c,A,end +start,b,A,b,A,end +start,b,A,b,end +start,b,A,c,A,b,A,end +start,b,A,c,A,b,end +start,b,A,c,A,c,A,end +start,b,A,c,A,end +start,b,A,end +start,b,d,b,A,c,A,end +start,b,d,b,A,end +start,b,d,b,end +start,b,end" + .lines() + .map(String::from) + .collect(); + dbg!(paths.symmetric_difference(&expected_paths)); + assert_eq!(paths, expected_paths); + } +} diff --git a/src/day_12/parsing.rs b/src/day_12/parsing.rs new file mode 100644 index 0000000..18acf78 --- /dev/null +++ b/src/day_12/parsing.rs @@ -0,0 +1,72 @@ +use yap::Tokens; + +use crate::parsing::{newline, parse_n}; + +use super::{Cave, CaveSystem}; + +pub fn parse_cave_system(tokens: &mut impl Tokens) -> Option { + let edges = tokens.sep_by(|t| parse_edge(t), |t| newline(t)).collect(); + CaveSystem::new(edges).ok() +} + +pub fn parse_edge(tokens: &mut impl Tokens) -> Option<[Cave; 2]> { + parse_n(tokens, &|t| parse_cave(t), &|t| t.token('-')) +} + +pub fn parse_cave(tokens: &mut impl Tokens) -> Option { + yap::one_of!(ts from tokens; + parse_start(ts), + parse_end(ts), + parse_big(ts), + parse_small(ts), + ) +} + +pub fn parse_start(tokens: &mut impl Tokens) -> Option { + tokens.tokens("start".chars()).then(|| Cave::Start) +} + +pub fn parse_end(tokens: &mut impl Tokens) -> Option { + tokens.tokens("end".chars()).then(|| Cave::End) +} + +pub fn parse_big(tokens: &mut impl Tokens) -> Option { + let cave: String = tokens.tokens_while(|c| c.is_ascii_uppercase()).collect(); + (!cave.is_empty()).then(|| Cave::Big(cave)) +} + +pub fn parse_small(tokens: &mut impl Tokens) -> Option { + let cave: String = tokens.tokens_while(|c| c.is_ascii_lowercase()).collect(); + (!cave.is_empty()).then(|| Cave::Small(cave)) +} + +#[cfg(test)] +mod test { + use yap::IntoTokens; + + use super::super::Cave; + + #[test] + fn parse_cave() { + assert_eq!( + super::parse_cave(&mut "start".into_tokens()), + Some(Cave::Start) + ); + assert_eq!(super::parse_cave(&mut "end".into_tokens()), Some(Cave::End)); + assert_eq!( + super::parse_cave(&mut "a".into_tokens()), + Some(Cave::Small(String::from("a"))) + ); + assert_eq!( + super::parse_cave(&mut "BBBB".into_tokens()), + Some(Cave::Big(String::from("BBBB"))) + ); + } + + #[test] + fn parse_edge() { + assert!(super::parse_edge(&mut "start-DX".into_tokens()).is_some()); + assert!(super::parse_edge(&mut "a-DX".into_tokens()).is_some()); + assert!(super::parse_edge(&mut "AAA-b".into_tokens()).is_some()); + } +} diff --git a/src/lib.rs b/src/lib.rs index a579490..9f6d250 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,5 +13,6 @@ pub mod day_08; pub mod day_09; pub mod day_10; pub mod day_11; +pub mod day_12; aoc_lib! { year = 2021 }