From d27f25036a7fa69c910029c3ca619c57e24e9b9c Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Mon, 27 Dec 2021 16:23:53 +0200 Subject: [PATCH] feat: solve day 12 part 1 & 2 --- input/12.txt | 24 ++++++++ src/day12.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 + 3 files changed, 187 insertions(+) create mode 100644 input/12.txt create mode 100644 src/day12.rs diff --git a/input/12.txt b/input/12.txt new file mode 100644 index 0000000..e1a2275 --- /dev/null +++ b/input/12.txt @@ -0,0 +1,24 @@ +pf-pk +ZQ-iz +iz-NY +ZQ-end +pf-gx +pk-ZQ +ZQ-dc +NY-start +NY-pf +NY-gx +ag-ZQ +pf-start +start-gx +BN-ag +iz-pf +ag-FD +pk-NY +gx-pk +end-BN +ag-pf +iz-pk +pk-ag +iz-end +iz-BN diff --git a/src/day12.rs b/src/day12.rs new file mode 100644 index 0000000..78d01e8 --- /dev/null +++ b/src/day12.rs @@ -0,0 +1,160 @@ +use std::collections::{HashMap, HashSet}; + +pub fn parse_input(input: &str) -> Vec<(String, String)> { + let mut edges: Vec<(String, String)> = Vec::new(); + + for line in input.lines() { + let (from, to) = line.split_once('-').unwrap(); + edges.push((from.into(), to.into())); + } + + return edges; +} + +fn edges_to_map(edges: &Vec<(String, String)>) -> HashMap> { + let mut map = HashMap::new(); + + for line in edges { + map.entry(line.1.clone()) + .or_insert(Vec::new()) + .push(line.0.clone()); + + map.entry(line.0.clone()) + .or_insert(Vec::new()) + .push(line.1.clone()); + } + + return map; +} + +fn is_path_finished(path: &Vec<&str>) -> bool { + return path.contains(&"end") +} + +fn can_be_appended_part1(path: &Vec<&str>, node: &str) -> bool { + node.to_uppercase() == node || !path.contains(&node) +} + +pub fn part1(edges: &Vec<(String, String)>) -> usize { + let map = edges_to_map(edges); + + let mut finished_paths: Vec> = Vec::new(); + + let mut unfinished_paths: Vec> = Vec::new(); + unfinished_paths.push(vec!["start"]); + while unfinished_paths.len() > 0 { + let mut new_paths = Vec::new(); + + for path in &mut unfinished_paths { + for node in map.get(*path.last().unwrap()).unwrap() { + if can_be_appended_part1(path, node) { + let mut new_path = path.clone(); + new_path.push(node); + + if is_path_finished(&new_path) { + finished_paths.push(new_path); + } else { + new_paths.push(new_path); + } + } + } + } + + unfinished_paths = new_paths; + } + + return finished_paths.len(); +} + +fn can_be_appended_part2(path: &Vec<&str>, node: &str) -> bool { + if node == "start" { return false; } + if node == "end" { return true; } + if node.to_uppercase() == node { return true; } + + // If all lowercase nodes only apear once we can be assure that any lowercase + // node that will be added will be correct. + let mut uniq = HashSet::new(); + if path.into_iter().all(move |x| x.to_lowercase() != *x || uniq.insert(x)) { + return true; + } + + return !path.contains(&node); +} + +pub fn part2(edges: &Vec<(String, String)>) -> usize { + let map = edges_to_map(edges); + + let mut finished_paths: Vec> = Vec::new(); + + let mut unfinished_paths: Vec> = Vec::new(); + unfinished_paths.push(vec!["start"]); + while unfinished_paths.len() > 0 { + let mut new_paths = Vec::new(); + + for path in &mut unfinished_paths { + for node in map.get(*path.last().unwrap()).unwrap() { + if can_be_appended_part2(path, node) { + let mut new_path = path.clone(); + new_path.push(node); + + if is_path_finished(&new_path) { + finished_paths.push(new_path); + } else { + new_paths.push(new_path); + } + } + } + } + + unfinished_paths = new_paths; + } + + return finished_paths.len(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + let cave_system = parse_input("start-A\nstart-b\nA-c\nA-b\nb-d\nA-end\nb-end"); + let result = part1(&cave_system); + assert_eq!(result, 10); + } + + #[test] + fn part1_larger_example() { + let cave_system = parse_input("dc-end\nHN-start\nstart-kj\ndc-start\ndc-HN\nLN-dc\nHN-end\nkj-sa\nkj-HN\nkj-dc"); + let result = part1(&cave_system); + assert_eq!(result, 19); + } + + #[test] + fn part1_largest_example() { + let cave_system = parse_input("fs-end\nhe-DX\nfs-he\nstart-DX\npj-DX\nend-zg\nzg-sl\nzg-pj\npj-he\nRW-he\nfs-DX\npj-RW\nzg-RW\nstart-pj\nhe-WI\nzg-he\npj-fs\nstart-RW"); + let result = part1(&cave_system); + assert_eq!(result, 226); + } + + #[test] + fn part2_example() { + let cave_system = parse_input("start-A\nstart-b\nA-c\nA-b\nb-d\nA-end\nb-end"); + let result = part2(&cave_system); + assert_eq!(result, 36); + } + + #[test] + fn part2_larger_example() { + let cave_system = parse_input("dc-end\nHN-start\nstart-kj\ndc-start\ndc-HN\nLN-dc\nHN-end\nkj-sa\nkj-HN\nkj-dc"); + let result = part2(&cave_system); + assert_eq!(result, 103); + } + + #[test] + fn part2_largest_example() { + let cave_system = parse_input("fs-end\nhe-DX\nfs-he\nstart-DX\npj-DX\nend-zg\nzg-sl\nzg-pj\npj-he\nRW-he\nfs-DX\npj-RW\nzg-RW\nstart-pj\nhe-WI\nzg-he\npj-fs\nstart-RW"); + let result = part2(&cave_system); + assert_eq!(result, 3509); + } +} diff --git a/src/main.rs b/src/main.rs index 4238e8c..ed3abf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod day8; mod day9; mod day10; mod day11; +mod day12; use std::{env, process}; use std::fs::File; @@ -47,6 +48,8 @@ fn run(day: i32, part: i32, input_filename: &str) { "10.2" => println!("{}", day10::part2(&day10::parse_input(&contents))), "11.1" => println!("{}", day11::part1(&day11::parse_input(&contents))), "11.2" => println!("{}", day11::part2(&day11::parse_input(&contents))), + "12.1" => println!("{}", day12::part1(&day12::parse_input(&contents))), + "12.2" => println!("{}", day12::part2(&day12::parse_input(&contents))), _ => println!("Day {} part {} not found", day, part) } }