diff options
Diffstat (limited to 'day12/__init__.py')
| -rw-r--r-- | day12/__init__.py | 116 |
1 files changed, 116 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) | ||
