diff options
Diffstat (limited to 'day12')
| -rw-r--r-- | day12/__init__.py | 116 | ||||
| -rw-r--r-- | day12/example.txt | 10 | ||||
| -rw-r--r-- | day12/input.txt | 23 | ||||
| -rw-r--r-- | day12/test_init.py | 72 |
4 files changed, 221 insertions, 0 deletions
diff --git a/day12/__init__.py b/day12/__init__.py new file mode 100644 index 0000000..aad531d --- /dev/null +++ b/day12/__init__.py | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | from abc import ABC | ||
| 2 | from collections import Counter | ||
| 3 | from dataclasses import dataclass, field | ||
| 4 | from typing import List, Tuple, Dict, Set | ||
| 5 | |||
| 6 | from aoc import BaseAssignment | ||
| 7 | |||
| 8 | Coordinate = Tuple[int, int] | ||
| 9 | Field = List[List[int]] | ||
| 10 | |||
| 11 | |||
| 12 | @dataclass | ||
| 13 | class Node: | ||
| 14 | id: str | ||
| 15 | big: bool = False | ||
| 16 | nodes: Set['Node'] = field(default_factory=set) | ||
| 17 | |||
| 18 | def __eq__(self, other: 'Node'): | ||
| 19 | return self.id == other.id | ||
| 20 | |||
| 21 | def __hash__(self): | ||
| 22 | return hash(self.id) | ||
| 23 | |||
| 24 | def __repr__(self): | ||
| 25 | return f'Node(id={self.id})' | ||
| 26 | |||
| 27 | def __lt__(self, other): | ||
| 28 | return self.id < other.id | ||
| 29 | |||
| 30 | class Assignment(BaseAssignment, ABC): | ||
| 31 | def parse_item(self, item: str) -> Tuple[str, str]: | ||
| 32 | return tuple(item.split('-')) | ||
| 33 | |||
| 34 | @classmethod | ||
| 35 | def get_or_create_node(cls, nodes: Dict[str, Node], id) -> Node: | ||
| 36 | if id not in nodes: | ||
| 37 | nodes[id] = Node(id=id, big=id.upper() == id) | ||
| 38 | return nodes[id] | ||
| 39 | |||
| 40 | def read_input(self, example = False) -> Dict[str, Node]: | ||
| 41 | nodes = {} | ||
| 42 | for a, b in super().read_input(example): | ||
| 43 | node_a = self.get_or_create_node(nodes, a) | ||
| 44 | node_b = self.get_or_create_node(nodes, b) | ||
| 45 | |||
| 46 | node_a.nodes.add(node_b) | ||
| 47 | node_b.nodes.add(node_a) | ||
| 48 | |||
| 49 | return nodes | ||
| 50 | |||
| 51 | @classmethod | ||
| 52 | def calculate_all_paths(cls, start: Node, end: Node, visited: List[Node] = []) -> List[List[Node]]: | ||
| 53 | raise NotImplementedError | ||
| 54 | |||
| 55 | def run(self, input: Dict[str, Node]) -> int: | ||
| 56 | return len(self.calculate_all_paths(input['start'], input['end'])) | ||
| 57 | |||
| 58 | |||
| 59 | class AssignmentOne(Assignment): | ||
| 60 | example_result = 19 | ||
| 61 | |||
| 62 | @classmethod | ||
| 63 | def calculate_all_paths(cls, start: Node, end: Node, visited: List[Node] = []) -> List[List[Node]]: | ||
| 64 | if start == end: | ||
| 65 | return [[end]] | ||
| 66 | |||
| 67 | return [ | ||
| 68 | [start, *path] | ||
| 69 | for node in start.nodes | ||
| 70 | if node.big or node not in visited | ||
| 71 | for path in cls.calculate_all_paths(node, end, visited=[*visited, start]) | ||
| 72 | ] | ||
| 73 | |||
| 74 | |||
| 75 | class AssignmentTwo(Assignment): | ||
| 76 | example_result = 103 | ||
| 77 | |||
| 78 | @classmethod | ||
| 79 | def can_be_visited(cls, node: Node, visited: List[Node]) -> bool: | ||
| 80 | if node.big: | ||
| 81 | return True | ||
| 82 | |||
| 83 | visited_counter = Counter([node for node in visited if not node.big]) | ||
| 84 | |||
| 85 | if node.id in ['start', 'end']: | ||
| 86 | return visited_counter.get(node, 0) < 1 | ||
| 87 | |||
| 88 | small_node_visited_twice = any( | ||
| 89 | v == 2 | ||
| 90 | for n, v | ||
| 91 | in visited_counter.items() | ||
| 92 | ) | ||
| 93 | |||
| 94 | return visited_counter.get(node, 0) < 1 if small_node_visited_twice else 2 | ||
| 95 | |||
| 96 | |||
| 97 | @classmethod | ||
| 98 | def calculate_all_paths(cls, start: Node, end: Node, visited: List[Node] = []) -> List[List[Node]]: | ||
| 99 | if start == end: | ||
| 100 | return [[end]] | ||
| 101 | |||
| 102 | return [ | ||
| 103 | [start, *path] | ||
| 104 | for node in start.nodes | ||
| 105 | if cls.can_be_visited(node, [*visited, start]) | ||
| 106 | for path in cls.calculate_all_paths(node, end, visited=[*visited, start]) | ||
| 107 | ] | ||
| 108 | |||
| 109 | def run(self, input: Dict[str, Node]) -> int: | ||
| 110 | paths = sorted(self.calculate_all_paths(input['start'], input['end'])) | ||
| 111 | |||
| 112 | print() | ||
| 113 | for path in paths: | ||
| 114 | print(','.join([ n.id for n in path ])) | ||
| 115 | |||
| 116 | return len(paths) | ||
diff --git a/day12/example.txt b/day12/example.txt new file mode 100644 index 0000000..62cc714 --- /dev/null +++ b/day12/example.txt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | dc-end | ||
| 2 | HN-start | ||
| 3 | start-kj | ||
| 4 | dc-start | ||
| 5 | dc-HN | ||
| 6 | LN-dc | ||
| 7 | HN-end | ||
| 8 | kj-sa | ||
| 9 | kj-HN | ||
| 10 | kj-dc | ||
diff --git a/day12/input.txt b/day12/input.txt new file mode 100644 index 0000000..96b504b --- /dev/null +++ b/day12/input.txt | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | vn-DD | ||
| 2 | qm-DD | ||
| 3 | MV-xy | ||
| 4 | end-xy | ||
| 5 | KG-end | ||
| 6 | end-kw | ||
| 7 | qm-xy | ||
| 8 | start-vn | ||
| 9 | MV-vn | ||
| 10 | vn-ko | ||
| 11 | lj-KG | ||
| 12 | DD-xy | ||
| 13 | lj-kh | ||
| 14 | lj-MV | ||
| 15 | ko-MV | ||
| 16 | kw-qm | ||
| 17 | qm-MV | ||
| 18 | lj-kw | ||
| 19 | VH-lj | ||
| 20 | ko-qm | ||
| 21 | ko-start | ||
| 22 | MV-start | ||
| 23 | DD-ko | ||
diff --git a/day12/test_init.py b/day12/test_init.py new file mode 100644 index 0000000..f6e78e1 --- /dev/null +++ b/day12/test_init.py | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | from day12 import Node, AssignmentOne, AssignmentTwo | ||
| 2 | |||
| 3 | |||
| 4 | def test_calculate_all_paths_assignment_one(): | ||
| 5 | start = Node(id='start') | ||
| 6 | end = Node(id='end') | ||
| 7 | |||
| 8 | start.nodes.add(end) | ||
| 9 | end.nodes.add(start) | ||
| 10 | |||
| 11 | assert AssignmentOne.calculate_all_paths(start, end) == [ | ||
| 12 | [start, end] | ||
| 13 | ] | ||
| 14 | |||
| 15 | a = Node(id='a') | ||
| 16 | start.nodes = { a } | ||
| 17 | a.nodes = { start, end } | ||
| 18 | end.nodes = { end } | ||
| 19 | |||
| 20 | assert AssignmentOne.calculate_all_paths(start, end) == [ | ||
| 21 | [start, a, end] | ||
| 22 | ] | ||
| 23 | |||
| 24 | b = Node(id='b') | ||
| 25 | start.nodes = { a, b } | ||
| 26 | b.nodes = { start, end } | ||
| 27 | a.nodes = { start, end } | ||
| 28 | end.nodes = { a, b } | ||
| 29 | |||
| 30 | assert sorted(AssignmentOne.calculate_all_paths(start, end)) == sorted([ | ||
| 31 | [start, a, end], | ||
| 32 | [start, b, end] | ||
| 33 | ]) | ||
| 34 | |||
| 35 | a.nodes.add(b) | ||
| 36 | b.nodes.add(a) | ||
| 37 | |||
| 38 | assert sorted(AssignmentOne.calculate_all_paths(start, end)) == sorted([ | ||
| 39 | [start, a, end], | ||
| 40 | [start, a, b, end], | ||
| 41 | [start, b, end], | ||
| 42 | [start, b, a, end] | ||
| 43 | ]) | ||
| 44 | |||
| 45 | a.big = True | ||
| 46 | |||
| 47 | assert sorted(AssignmentOne.calculate_all_paths(start, end)) == sorted([ | ||
| 48 | [start, a, end], | ||
| 49 | [start, a, b, end], | ||
| 50 | [start, a, b, a, end], | ||
| 51 | [start, b, end], | ||
| 52 | [start, b, a, end], | ||
| 53 | ]) | ||
| 54 | |||
| 55 | |||
| 56 | def test_calculate_all_paths_assignment_two(): | ||
| 57 | start = Node(id='start') | ||
| 58 | end = Node(id='end') | ||
| 59 | |||
| 60 | a = Node(id='a') | ||
| 61 | b = Node(id='b') | ||
| 62 | |||
| 63 | start.nodes.add(a) | ||
| 64 | a.nodes.add(b) | ||
| 65 | a.nodes.add(start) | ||
| 66 | b.nodes.add(a) | ||
| 67 | b.nodes.add(end) | ||
| 68 | end.nodes.add(b) | ||
| 69 | |||
| 70 | assert sorted(AssignmentTwo.calculate_all_paths(start, end)) == sorted([ | ||
| 71 | [start, a, b, end] | ||
| 72 | ]) | ||
