summaryrefslogtreecommitdiffstats
path: root/day12/__init__.py
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2021-12-13 10:20:50 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2021-12-13 10:20:50 +0100
commit7bbeb8155521686bd9bbd28fec5cb91ffb8360af (patch)
treed0f272a59681223e0af204363b500a1f78626f09 /day12/__init__.py
parent2de80670eb989467111da967ccc78de7c00f06bc (diff)
download2021-7bbeb8155521686bd9bbd28fec5cb91ffb8360af.tar.gz
2021-7bbeb8155521686bd9bbd28fec5cb91ffb8360af.tar.bz2
2021-7bbeb8155521686bd9bbd28fec5cb91ffb8360af.zip
Day 12
Diffstat (limited to 'day12/__init__.py')
-rw-r--r--day12/__init__.py116
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 @@
1from abc import ABC
2from collections import Counter
3from dataclasses import dataclass, field
4from typing import List, Tuple, Dict, Set
5
6from aoc import BaseAssignment
7
8Coordinate = Tuple[int, int]
9Field = List[List[int]]
10
11
12@dataclass
13class 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
30class 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
59class 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
75class 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)