diff --git a/Cargo.toml b/Cargo.toml index d1e791f..d208614 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,8 @@ aoc_2024 = { path = "aoc_2024" } clap = { version = "4.0.29", features = ["derive"] } chrono = "0.4.31" anyhow = "1.0.75" + + +[profile.release] +overflow-checks = true +incremental = true diff --git a/README.md b/README.md index c51e0e9..b575f8e 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even - [Day 09: Smoke Basin](aoc_2021/src/day_09.rs) - [Day 10: Syntax Scoring](aoc_2021/src/day_10.rs) - [Day 11: Dumbo Octopus](aoc_2021/src/day_11.rs) +- [Day 12: Passage Pathing](aoc_2021/src/day_12.rs) - [Day 13: Transparent Origami](aoc_2021/src/day_13.rs) +- [Day 14: Extended Polymerization](aoc_2021/src/day_14.rs) +- [Day 15: Chiton](aoc_2021/src/day_15.rs) diff --git a/aoc_2021/src/day_12.rs b/aoc_2021/src/day_12.rs index ee14e97..9739622 100644 --- a/aoc_2021/src/day_12.rs +++ b/aoc_2021/src/day_12.rs @@ -6,27 +6,56 @@ solution!("Passage Pathing", 12); fn part_a(input: &str) -> Answer { let graph = parse(input); - paths_a(&graph, graph.start, HashSet::new()).into() -} -fn part_b(_input: &str) -> Answer { - Answer::Unimplemented -} + fn paths(graph: &ParseResult, at: NodeIndex, mut visited: HashSet) -> usize { + if at == graph.end { + return 1; + } + + if !visited.insert(at) && graph.graph[at].cave_type != Type::Big { + return 0; + } -fn paths_a(graph: &ParseResult, at: NodeIndex, mut visited: HashSet) -> usize { - if at == graph.end { - return 1; + graph + .graph + .neighbors(at) + .map(|child| paths(graph, child, visited.clone())) + .sum() } - if !visited.insert(at) && graph.graph[at].cave_type != Type::Big { - return 0; + paths(&graph, graph.start, HashSet::new()).into() +} + +fn part_b(input: &str) -> Answer { + let graph = parse(input); + + fn paths( + graph: &ParseResult, + at: NodeIndex, + mut visited: HashSet, + mut small_twice: bool, + ) -> usize { + if at == graph.end { + return 1; + } + + let cave = graph.graph[at].cave_type; + if !visited.insert(at) && cave != Type::Big { + if !small_twice && cave == Type::Small { + small_twice = true; + } else { + return 0; + } + } + + graph + .graph + .neighbors(at) + .map(|child| paths(graph, child, visited.clone(), small_twice)) + .sum() } - graph - .graph - .neighbors(at) - .map(|child| paths_a(graph, child, visited.clone())) - .sum() + paths(&graph, graph.start, HashSet::new(), false).into() } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -130,6 +159,7 @@ mod test { #[test] fn part_b() { - assert_eq!(super::part_b(CASE), ().into()); // 36 + assert_eq!(super::part_b(CASE), 36.into()); + assert_eq!(super::part_b(CASE_2), 103.into()); } } diff --git a/aoc_2021/src/day_14.rs b/aoc_2021/src/day_14.rs index 9d3244b..c17ab8b 100644 --- a/aoc_2021/src/day_14.rs +++ b/aoc_2021/src/day_14.rs @@ -8,18 +8,13 @@ fn part_a(input: &str) -> Answer { process(input, 10).into() } -// TODO: work with counts of units instead of the units themselves fn part_b(input: &str) -> Answer { - let mut _polymer = Polymer::parse(input); - Answer::Unimplemented + process(input, 40).into() } -fn process(raw: &str, steps: usize) -> usize { - let mut polymer = Polymer::parse(raw); - (0..steps).for_each(|_| polymer.step()); - - let (min, max) = polymer.min_max(); - max - min +fn process(raw: &str, steps: usize) -> u64 { + let counts = Polymer::parse(raw).process(steps); + counts.iter().max().unwrap() - counts.iter().filter(|&&x| x != 0).min().unwrap() } #[derive(Debug)] @@ -29,29 +24,38 @@ struct Polymer { } impl Polymer { - fn step(&mut self) { - let mut next = Vec::new(); - for i in self.units.windows(2) { - next.push(i[0]); + fn process(&mut self, steps: usize) -> [u64; 26] { + fn index(unit: char) -> usize { + unit as usize - 'A' as usize + } - if let Some(i) = self.key.get(i) { - next.push(*i); - } + let mut pairs = HashMap::<_, u64>::new(); + let mut counts = [0; 26]; + + counts[index(*self.units.last().unwrap())] += 1; + for units in self.units.windows(2) { + counts[index(units[0])] += 1; + *pairs.entry([units[0], units[1]]).or_default() += 1; } - next.push(*self.units.last().unwrap()); - self.units = next; - } + // AB -> C + // (A, B) -> (A, C), (C, B) + // C += 1 + for _ in 0..steps { + let mut new_pairs = HashMap::new(); - fn min_max(&self) -> (usize, usize) { - let mut out = HashMap::new(); - self.units - .iter() - .for_each(|i| *out.entry(*i).or_insert(0) += 1); + for (pair, count) in pairs.iter() { + let mapping = self.key[pair]; + + *new_pairs.entry([pair[0], mapping]).or_default() += count; + *new_pairs.entry([mapping, pair[1]]).or_default() += count; + counts[index(mapping)] += count; + } + + pairs = new_pairs; + } - let mut out = out.into_iter().collect::>(); - out.sort_by(|a, b| a.1.cmp(&b.1)); - (out[0].1, out[out.len() - 1].1) + counts } fn parse(raw: &str) -> Self { @@ -73,3 +77,39 @@ impl Polymer { } } } + +#[cfg(test)] +mod test { + use indoc::indoc; + + const CASE: &str = indoc! {" + NNCB + + CH -> B + HH -> N + CB -> H + NH -> C + HB -> C + HC -> B + HN -> C + NN -> C + BH -> H + NC -> B + NB -> B + BN -> B + BB -> N + BC -> B + CC -> N + CN -> C + "}; + + #[test] + fn part_a() { + assert_eq!(super::part_a(CASE), 1588.into()); + } + + #[test] + fn part_b() { + assert_eq!(super::part_b(CASE), 2188189693529_u64.into()); + } +}