summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--aoc/mixins.py57
-rw-r--r--day12/__init__.py65
-rw-r--r--day16/__init__.py60
-rw-r--r--day16/example.txt10
-rw-r--r--day16/input.txt59
5 files changed, 196 insertions, 55 deletions
diff --git a/aoc/mixins.py b/aoc/mixins.py
new file mode 100644
index 0000000..faeb3eb
--- /dev/null
+++ b/aoc/mixins.py
@@ -0,0 +1,57 @@
1# -*- coding: utf-8 -*-
2from math import floor, inf
3from typing import Tuple, TypeVar, Generic, Callable, Iterator, List, Dict
4
5Node = TypeVar("Node")
6
7
8class AStarMixin(Generic[Node]):
9 def _gen_path(self, current: Node, came_from: Dict[Node, Node]) -> List[Node]:
10 print("GENPATH")
11 path = [current]
12
13 while current in came_from:
14 current = came_from[current]
15 path = [current, *path]
16
17 return path
18
19 def a_star(
20 self,
21 start: Node,
22 end: Callable[[Node, Callable[[], List[Node]]], bool],
23 neighbours: Callable[[Node], Iterator[Node]],
24 distance: Callable[[Node, Node], int] = lambda a, b: 1,
25 heuristic: Callable[[Node], int] = lambda a: 0,
26 ) -> List[Node]:
27 open_nodes = {start}
28 came_from = {}
29
30 g_scores = {start: 0}
31
32 f_scores = {start: heuristic(start)}
33
34 while True:
35 try:
36 current = sorted(
37 [item for item in open_nodes],
38 key=lambda item: f_scores.get(item, inf),
39 )[0]
40 except IndexError:
41 raise RuntimeError("No path found")
42
43 if end(current, lambda: self._gen_path(current, came_from)):
44 return self._gen_path(current, came_from)
45
46 open_nodes.remove(current)
47
48 for neighbour in neighbours(current):
49 g_score = g_scores.get(current, inf) + distance(current, neighbour)
50
51 if g_score < g_scores.get(neighbour, inf):
52 came_from[neighbour] = current
53 g_scores[neighbour] = g_score
54 f_scores[neighbour] = g_score + heuristic(neighbour)
55
56 if neighbour not in open_nodes:
57 open_nodes.add(neighbour)
diff --git a/day12/__init__.py b/day12/__init__.py
index 69f9458..ef03ee0 100644
--- a/day12/__init__.py
+++ b/day12/__init__.py
@@ -5,6 +5,7 @@ from math import inf, sqrt, floor
5from typing import List, Tuple, Iterator, Callable, Any 5from typing import List, Tuple, Iterator, Callable, Any
6 6
7from aoc import BaseAssignment 7from aoc import BaseAssignment
8from aoc.mixins import AStarMixin
8 9
9 10
10class Map: 11class Map:
@@ -57,7 +58,7 @@ class Map:
57 return elevation - 96 58 return elevation - 96
58 59
59 60
60class Assignment(BaseAssignment, ABC): 61class Assignment(BaseAssignment, AStarMixin[Tuple[int, int]], ABC):
61 def distance(self, map: Map, a: Tuple[int, int], b: Tuple[int, int]): 62 def distance(self, map: Map, a: Tuple[int, int], b: Tuple[int, int]):
62 distance = map.elevation(*b) - map.elevation(*a) 63 distance = map.elevation(*b) - map.elevation(*a)
63 64
@@ -66,67 +67,22 @@ class Assignment(BaseAssignment, ABC):
66 67
67 return 1 68 return 1
68 69
70
71class AssignmentOne(Assignment):
72 example_result = 31
73
69 def heuristic(self, map: Map, a: Tuple[int, int]): 74 def heuristic(self, map: Map, a: Tuple[int, int]):
70 dx = abs(map.start[0] - a[0]) 75 dx = abs(map.start[0] - a[0])
71 dy = abs(map.start[1] - a[1]) 76 dy = abs(map.start[1] - a[1])
72 return floor(sqrt(dx**2 + dy**2)) 77 return floor(sqrt(dx**2 + dy**2))
73 78
74 def a_star(
75 self,
76 map: Map,
77 start: Tuple[int, int],
78 end: Callable[[int, int], bool],
79 distance: Callable[[Tuple[int, int], Tuple[int, int]], int],
80 heuristic: Callable[[Tuple[int, int]], int],
81 ) -> List[Tuple[int, int]]:
82 open = {start}
83 came_from = {}
84
85 g_scores = {start: 0}
86
87 f_scores = {start: heuristic(start)}
88
89 while True:
90 try:
91 current = sorted(
92 [item for item in open], key=lambda item: f_scores.get(item, inf)
93 )[0]
94 except IndexError:
95 raise RuntimeError("No path found")
96
97 if end(*current):
98 path = [current]
99
100 while current in came_from:
101 current = came_from[current]
102 path = [current, *path]
103
104 return path
105
106 open.remove(current)
107
108 for neighbour in map.neighbours(*current):
109 g_score = g_scores.get(current, inf) + distance(current, neighbour)
110
111 if g_score < g_scores.get(neighbour, inf):
112 came_from[neighbour] = current
113 g_scores[neighbour] = g_score
114 f_scores[neighbour] = g_score + heuristic(neighbour)
115
116 if neighbour not in open:
117 open.add(neighbour)
118
119
120class AssignmentOne(Assignment):
121 example_result = 31
122
123 def run(self, input: Iterator) -> Any: 79 def run(self, input: Iterator) -> Any:
124 map = Map(map=list(input)) 80 map = Map(map=list(input))
125 81
126 path = self.a_star( 82 path = self.a_star(
127 map,
128 map.start, 83 map.start,
129 lambda x, y: (x, y) == map.end, 84 lambda coordinate, _: coordinate == map.end,
85 neighbours=lambda a: map.neighbours(*a),
130 distance=lambda a, b: self.distance(map, a, b), 86 distance=lambda a, b: self.distance(map, a, b),
131 heuristic=lambda a: self.heuristic(map, a), 87 heuristic=lambda a: self.heuristic(map, a),
132 ) 88 )
@@ -141,11 +97,10 @@ class AssignmentTwo(Assignment):
141 map = Map(list(input)) 97 map = Map(list(input))
142 98
143 path = self.a_star( 99 path = self.a_star(
144 map,
145 map.end, 100 map.end,
146 lambda x, y: map.elevation(x, y) == 1, 101 lambda coordinate, _: map.elevation(*coordinate) == 1,
102 neighbours=lambda a: map.neighbours(*a),
147 distance=lambda a, b: self.distance(map, b, a), 103 distance=lambda a, b: self.distance(map, b, a),
148 heuristic=lambda a: 1,
149 ) 104 )
150 105
151 return len(path) - 1 106 return len(path) - 1
diff --git a/day16/__init__.py b/day16/__init__.py
new file mode 100644
index 0000000..ecf33c5
--- /dev/null
+++ b/day16/__init__.py
@@ -0,0 +1,60 @@
1# -*- coding: utf-8 -*-
2import re
3from abc import ABC
4from dataclasses import dataclass
5from typing import List, Iterator, Dict
6
7from aoc import BaseAssignment
8from aoc.mixins import AStarMixin
9
10
11@dataclass
12class Valve:
13 name: str
14 flow_rate: int
15 tunnels_to: List[str]
16
17 def __hash__(self):
18 return hash(self.name)
19
20
21valve_pattern = re.compile(
22 "Valve (\w+) has flow rate=(\d+); tunnels? leads? to valves? (.*)"
23)
24
25
26class Assignment(BaseAssignment[int, Valve], AStarMixin[Valve], ABC):
27 def parse_item(self, item: str) -> Valve:
28 match = valve_pattern.match(item)
29 name, flow_rate, tunnels_to = match.groups()
30
31 return Valve(
32 name=name,
33 tunnels_to=tunnels_to.split(", "),
34 flow_rate=int(flow_rate),
35 )
36
37 @staticmethod
38 def valve_map(input: Iterator[Valve]) -> Dict[str, Valve]:
39 return {valve.name: valve for valve in input}
40
41
42class AssignmentOne(Assignment):
43 example_result = 1651
44
45 def run(self, input: Iterator[Valve]) -> int:
46 valves = self.valve_map(input)
47
48 queue = [valves["AA"]]
49 open = {}
50 minutes = 30
51
52 while len(queue) > 0 or minutes > 0:
53 valve = queue.pop(0)
54
55 for v in valve.tunnels_to:
56 pass
57
58
59class AssignmentTwo(Assignment):
60 pass