From c0d352cdf14c23a434a23b710cf1773b9d62b97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Ytterstr=C3=B6m?= Date: Sun, 12 Dec 2021 14:28:36 +0100 Subject: [PATCH] Solve 2021:12 "Passage Pathing" I struggled a lot with the direction, but realised way too much later that the mappings are bidirectional. Part 2 was exceptionally hard, but I realised early that the order did not matter and that a simple boolean lock was all that was needed. The challenge was to know when to watch for duplicates. The seen was initially a set, but since new sets must be sent instead of references, a list is used instead to be able to use the seen + [] hack. --- 2021-python/solutions/day_12.py | 54 +++++++++++++++++++++++ 2021-python/tests/test_day_12.py | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 2021-python/solutions/day_12.py create mode 100644 2021-python/tests/test_day_12.py diff --git a/2021-python/solutions/day_12.py b/2021-python/solutions/day_12.py new file mode 100644 index 0000000..724eac3 --- /dev/null +++ b/2021-python/solutions/day_12.py @@ -0,0 +1,54 @@ +from solutions import BaseSolution +from collections import defaultdict, deque, Counter + + +class Solution(BaseSolution): + input_file = "12.txt" + + def __str__(self): + return "Day 12: Passage Pathing" + + def parse_input(self, data): + return [l.split("-") for l in data.split()] + + def solve(self, puzzle_input): + return self._solve(puzzle_input, lambda seen: True) + + def solve_again(self, puzzle_input): + return self._solve( + puzzle_input, + lambda seen: any( + [ + k.islower() and k != "start" and v > 1 + for k, v in Counter(seen).items() + ] + ), + ) + + def _solve(self, puzzle_input, isdup): + ft = defaultdict(lambda: []) + for f, t in puzzle_input: + ft[f].append(t) + for f, t in puzzle_input: + ft[t].append(f) + queue = deque([("start", list(), False)]) + s2e = 0 + while queue: + pos, seen, dup = queue.pop() + seen.append(pos) + dup = isdup(seen) + for t in ft[pos]: + if t == "end": + s2e += 1 + continue + elif t == "start": + continue + elif dup and t.islower() and t in seen: + continue + queue.append((t, seen + [], dup)) + return s2e + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2021-python/tests/test_day_12.py b/2021-python/tests/test_day_12.py new file mode 100644 index 0000000..f224735 --- /dev/null +++ b/2021-python/tests/test_day_12.py @@ -0,0 +1,75 @@ +import unittest + +from solutions.day_12 import Solution + + +class Day12TestCase(unittest.TestCase): + def setUp(self): + self.solution = Solution() + self.puzzle_input = self.solution.parse_input( + """ + start-A +start-b +A-c +A-b +b-d +A-end +b-end + """ + ) + + def test_parse_puzzle_input(self): + data = """ + a-b + q1w2-e3r4 + """ + assert self.solution.parse_input(data) == [["a", "b"], ["q1w2", "e3r4"]] + + def test_solve_first_part(self): + assert self.solution.solve(self.puzzle_input) == 10 + + def test_solve_second_part(self): + assert self.solution.solve_again(self.puzzle_input) == 36 + + +if __name__ == "__main__": + unittest.main() + +""" +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 +"""