diff --git a/input/day12 b/input/day12 new file mode 100644 index 0000000..012ca7e --- /dev/null +++ b/input/day12 @@ -0,0 +1,21 @@ +KF-sr +OO-vy +start-FP +FP-end +vy-mi +vy-KF +vy-na +start-sr +FP-lh +sr-FP +na-FP +end-KF +na-mi +lh-KF +end-lh +na-start +wp-KF +mi-KF +vy-sr +vy-lh +sr-mi \ No newline at end of file diff --git a/src/day11.rs b/src/day11.rs index 9701fc6..2db219b 100644 --- a/src/day11.rs +++ b/src/day11.rs @@ -81,6 +81,7 @@ fn do_flash(mut hm: &mut FlashMap, mut flashing: &mut Vec<(i32, i32)>, mut flash } } +#[allow(unused)] fn print_flashes(map: &FlashMap, x_max: i32, y_max: i32) { let mut v: Vec> = Vec::new(); for y in 0..y_max { diff --git a/src/day12.rs b/src/day12.rs new file mode 100644 index 0000000..a0b75aa --- /dev/null +++ b/src/day12.rs @@ -0,0 +1,275 @@ +use std::collections::HashMap; +use crate::Day; + +pub struct Day12(HashMap); + +impl Day for Day12 { + fn init(content: String) -> anyhow::Result { + let mut caves = HashMap::new(); + + for line in content.lines() { + let z = line.split('-').collect::>(); + let first = z[0]; + let second = z[1]; + + { + let first_cave = caves.entry(first.to_string()).or_insert(Cave::new(first.into())); + first_cave.add_connection(second); + } + { + let second_cave = caves.entry(second.to_string()).or_insert(Cave::new(second.into())); + second_cave.add_connection(first); + } + } + + Ok(Self(caves)) + } + + fn part1(&self) -> anyhow::Result { + fn get_all_paths(caves: &HashMap, complete_paths: &mut Vec>, current_path: &Vec, current_cave_idx: &str) { + if current_cave_idx == "end" { + complete_paths.push(current_path.clone()); + return; + } + + let current_cave = caves.get(current_cave_idx).expect("This cave does not exist!").clone(); + + for connected_cave in current_cave.connections { + let current = caves.get(&connected_cave).unwrap(); + match current.cave_type { + CaveType::Small => { + if current_path.iter().find(|&cave_pair| cave_pair == &connected_cave) != None { + continue; + } + } + _ => (), + } + + let mut new_path = current_path.clone(); + new_path.push(connected_cave.clone()); + get_all_paths(&caves, complete_paths, &new_path, &connected_cave); + } + } + + let mut complete_paths = Vec::new(); + let current_path = vec!["start".into()]; + + get_all_paths(&self.0, &mut complete_paths, ¤t_path, "start".into()); + + Ok(format!("{}", complete_paths.len())) + } + + fn part2(&self) -> anyhow::Result { + let mut small_caves_thing: HashMap, Vec> = HashMap::, Vec>::new(); + fn get_all_paths(caves: &HashMap, complete_paths: &mut Vec>, current_path: &Vec, current_cave_idx: &str, small_caves_thing: &mut HashMap, Vec>) { + if current_cave_idx == "end" { + complete_paths.push(current_path.clone()); + return; + } + + + let current_cave = caves.get(current_cave_idx).expect("This cave does not exist!").clone(); + + let small_caves = { + let e = small_caves_thing.entry(current_path.clone()).or_insert(small_caves_visited_at_least_twice(caves, current_path)); + e.clone() + }; + + for connected_cave in current_cave.connections { + if connected_cave == "start" { + continue; + } + if !small_caves.is_empty() { + let current = caves.get(&connected_cave).unwrap(); + match current.cave_type { + CaveType::Small => { + if !(small_caves.len() == 1 && small_caves[0] != connected_cave) { + continue; + } + } + _ => (), + } + } + + let mut new_path = current_path.clone(); + new_path.push(connected_cave.clone()); + get_all_paths(&caves, complete_paths, &new_path, &connected_cave, small_caves_thing); + } + } + + let mut complete_paths = Vec::new(); + let current_path = vec!["start".into()]; + + get_all_paths(&self.0, &mut complete_paths, ¤t_path, "start", &mut small_caves_thing); + + Ok(format!("{}", complete_paths.len())) + } +} + +fn small_caves_visited_at_least_twice(caves: &HashMap, current_path: &Vec) -> Vec { + let mut small_cave_indices = HashMap::new(); + for cave_name in current_path { + let c = caves.get(cave_name).expect("how?"); + if c.cave_type == CaveType::Small { + let e = small_cave_indices.entry(cave_name).or_insert(0); + *e += 1; + } + } + return small_cave_indices.iter().filter(|(_, &b)| b > 1).map(|(&a, _)| a.clone()).collect(); +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Copy, Clone)] +enum CaveType { + Large, + Small, +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Clone)] +struct Cave { + connections: Vec, + cave_type: CaveType, +} + +impl Cave { + fn add_connection(&mut self, name: T) where T: ToString { + self.connections.push(name.to_string()); + self.connections.sort(); + self.connections.dedup(); + } + + fn new(cave_name: String) -> Self { + let cave_type = if cave_name.chars().nth(0).unwrap().is_uppercase() { + CaveType::Large + } else { + CaveType::Small + }; + Self { + cave_type, + connections: vec![], + } + } +} + +impl Day12 { + fn search(&self, cur: &str, had: &HashMap, part1: bool) -> i32 { + let mut had = had.clone(); + if had.values().any(|&i| i > 0) || part1 { + if had.contains_key(cur) { + return 0; + } + } + + if cur == "start" && had.contains_key("start") { + return 0; + } + if cur == "end" { + return 1; + } + if cur.chars().all(|x| x.is_lowercase()) { + let e = had.entry(cur.into()).or_insert(0); + *e += 1; + } + let n = self.0.get(cur).unwrap(); + return n.connections.iter().map(|c| self.search(c, &mut had, part1)).sum::(); + } +} + +#[cfg(test)] +mod tests { + use crate::{Day, Day12}; + + const INPUT_1: &str = r"start-A +start-b +A-c +A-b +b-d +A-end +b-end"; + + const INPUT_2: &str = r"dc-end +HN-start +start-kj +dc-start +dc-HN +LN-dc +HN-end +kj-sa +kj-HN +kj-dc"; + + const INPUT_3: &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"; + + #[test] + fn part1_test_1() -> anyhow::Result<()> { + let d = Day12::init(INPUT_1.to_string())?; + assert_eq!("10", d.part1()?); + Ok(()) + } + + #[test] + fn part1_test_2() -> anyhow::Result<()> { + let d = Day12::init(INPUT_2.to_string())?; + assert_eq!("19", d.part1()?); + Ok(()) + } + + #[test] + fn part1_test_3() -> anyhow::Result<()> { + let d = Day12::init(INPUT_3.to_string())?; + assert_eq!("226", d.part1()?); + Ok(()) + } + + // #[test] + // fn part1_real() -> anyhow::Result<()> { + // let d = Day12::init(crate::load_input("12")?)?; + // assert_eq!("4885", d.part1()?); + // Ok(()) + // } + + #[test] + fn part2_test_1() -> anyhow::Result<()> { + let d = Day12::init(INPUT_1.to_string())?; + assert_eq!("36", d.part2()?); + Ok(()) + } + + #[test] + fn part2_test_2() -> anyhow::Result<()> { + let d = Day12::init(INPUT_2.to_string())?; + assert_eq!("103", d.part2()?); + Ok(()) + } + + // These are very slow, so they are commented out. + // #[test] + // fn part2_test_3() -> anyhow::Result<()> { + // let d = Day12::init(INPUT_3.to_string())?; + // assert_eq!("3509", d.part2()?); + // Ok(()) + // } + + #[test] + fn part2_real() -> anyhow::Result<()> { + let d = Day12::init(crate::load_input("12")?)?; + assert_eq!("117095", d.part2()?); + Ok(()) + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index fa76862..b81e939 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use crate::day08::Day08; use crate::day09::Day09; use crate::day10::Day10; use crate::day11::Day11; +use crate::day12::Day12; mod day; mod day01; @@ -27,6 +28,7 @@ mod day08; mod day09; mod day10; mod day11; +mod day12; fn load_input(day: &str) -> Result { read_to_string(format!("./input/day{}", day)).map_err(|x| x.into()) @@ -72,7 +74,8 @@ fn main() -> anyhow::Result<()> { Box::new(Day08::init(load_input("08")?)?), Box::new(Day09::init(load_input("09")?)?), Box::new(Day10::init(load_input("10")?)?), - Box::new(Day11::init(load_input("11")?)?),]; + Box::new(Day11::init(load_input("11")?)?), + Box::new(Day12::init(load_input("12")?)?),]; let _verbosity = matches.occurrences_of("v"); if matches.is_present("all") {