From dffb8a86733303527eab798e44c3d87baccff012 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Tue, 28 Dec 2021 15:14:08 -0800 Subject: [PATCH] day 12 --- 2021/src/day12.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++++ 2021/src/lib.rs | 5 +- 2021/src/main.rs | 6 +- 3 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 2021/src/day12.rs diff --git a/2021/src/day12.rs b/2021/src/day12.rs new file mode 100644 index 0000000..1a96e9d --- /dev/null +++ b/2021/src/day12.rs @@ -0,0 +1,174 @@ +use std::collections::{HashMap, HashSet}; +use std::str::FromStr; + +use color_eyre::eyre; +use eyre::eyre; + +use crate::lib::IterExt; + + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +enum Size { + Big, + Small, +} + + +// this is a terrible way to do it, because it's completely tied to +// the dataset and needs to be redone in order to e.g. use a sample +// dataset. A better way would be to use [char; 2] to represent each +// cave and supply a method that computes size from letter case. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[allow(non_camel_case_types)] +enum Tag { + start, + de, + DN, + jv, + MU, + oa, + OC, + rj, + vd, + VP, + WK, + yb, + end, +} + + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +struct Cave { + tag: Tag, + size: Size, +} + + +impl FromStr for Cave { + type Err = eyre::Report; + fn from_str(s: &str) -> eyre::Result { + let cave = match s { + "start" => Cave {tag: Tag::start, size: Size::Small}, + "de" => Cave {tag: Tag::de, size: Size::Small}, + "DN" => Cave {tag: Tag::DN, size: Size::Big}, + "jv" => Cave {tag: Tag::jv, size: Size::Small}, + "MU" => Cave {tag: Tag::MU, size: Size::Big}, + "oa" => Cave {tag: Tag::oa, size: Size::Small}, + "OC" => Cave {tag: Tag::OC, size: Size::Big}, + "rj" => Cave {tag: Tag::rj, size: Size::Small}, + "vd" => Cave {tag: Tag::vd, size: Size::Small}, + "VP" => Cave {tag: Tag::VP, size: Size::Big}, + "WK" => Cave {tag: Tag::WK, size: Size::Big}, + "yb" => Cave {tag: Tag::yb, size: Size::Small}, + "end" => Cave {tag: Tag::end, size: Size::Small}, + _ => return Err(eyre!("Invalid cave tag: {}", s)), + }; + Ok(cave) + } +} + + +type CaveMap = HashMap>; + + +struct Path { + order: Vec, + visited: HashSet, + doubled: Option +} + +impl Path { + fn new() -> Self { + Path { + order: vec![Cave {tag: Tag::start, size: Size::Small}], + visited: HashSet::new(), + doubled: None, + } + } + + fn push(&mut self, cave: Cave) { + self.order.push(cave); + if cave.size == Size::Small { + let inserted = self.visited.insert(cave); + if !inserted { // value was already present in set + self.doubled = Some(cave); + } + } + } + + fn pop(&mut self) { + let cave = self.order.pop().unwrap(); + if cave.size == Size::Small { + match self.doubled { + Some(d) if d == cave => {self.doubled = None}, + _ => {self.visited.remove(&cave);} + } + } + } + + fn last(&self) -> Option<&Cave> { + self.order.last() + } +} + + +fn load(data: &str) -> eyre::Result { + let mut map = HashMap::new(); + for line in data.lines() { + let (a, b) = line.split('-').take_pair()?; + let cave_a = a.parse::()?; + let cave_b = b.parse::()?; + + let neighbors_a = map.entry(cave_a.tag).or_insert_with(|| vec![]); + neighbors_a.push(cave_b); + + let neighbors_b = map.entry(cave_b.tag).or_insert_with(|| vec![]); + neighbors_b.push(cave_a); + } + Ok(map) +} + + +fn can_visit_part1(path: &Path, cave: &Cave) -> bool { + if cave.size == Size::Big {true} + else if cave.tag == Tag::start {false} + else if path.visited.contains(cave) {false} + else {true} +} + +fn can_visit_part2(path: &Path, cave: &Cave) -> bool { + if cave.size == Size::Big {true} + else if cave.tag == Tag::start {false} + else if path.visited.contains(cave) && path.doubled.is_some() {false} + else {true} +} + + +fn search(map: &CaveMap, path: &mut Path, can_visit: F) -> Vec> +where F: Fn(&Path, &Cave) -> bool + Copy +{ + let mut result = Vec::new(); + for neighbor in &map[&path.last().unwrap().tag] { + if neighbor.tag == Tag::end { + path.push(*neighbor); + result.push(path.order.clone()); + path.pop(); + } + else if can_visit(path, neighbor) { + path.push(*neighbor); + let child_paths = search(map, path, can_visit); + result.extend(child_paths); + path.pop(); + } + } + result +} + + +pub fn run(data: &str) -> eyre::Result<(usize, usize)> { + let map = load(data)?; + let one = search(&map, &mut Path::new(), can_visit_part1); + let two = search(&map, &mut Path::new(), can_visit_part2); + + Ok((one.len(), two.len())) +} diff --git a/2021/src/lib.rs b/2021/src/lib.rs index ba532f8..6787be7 100644 --- a/2021/src/lib.rs +++ b/2021/src/lib.rs @@ -133,9 +133,8 @@ impl Iterator for NeighborCoords { let row_offset = self.start_row + rel_row; let col_offset = self.start_col + rel_col; // the "neighbor window" is off by 1, so we make the comparisons off by 1 as well - if - (row_offset > 0 && row_offset <= self.num_rows - && col_offset > 0 && col_offset <= self.num_cols) + if row_offset > 0 && row_offset <= self.num_rows + && col_offset > 0 && col_offset <= self.num_cols { Some((row_offset - 1, col_offset - 1)) } diff --git a/2021/src/main.rs b/2021/src/main.rs index 478f56f..1696cad 100644 --- a/2021/src/main.rs +++ b/2021/src/main.rs @@ -4,14 +4,14 @@ use color_eyre::eyre; mod lib; use lib::load; -mod day11; +mod day12; fn main() -> eyre::Result<()> { - let data = load("data/11.txt")?; + let data = load("data/12.txt")?; let start = Instant::now(); - let (one, two) = day11::run(&data)?; + let (one, two) = day12::run(&data)?; let (dur, unit) = format_ns(start.elapsed().as_nanos()); let precision = 2.0 - dur.log10().floor();