summaryrefslogtreecommitdiffstats
path: root/day15/__init__.py
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2021-12-15 17:20:46 +0100
committerGravatar Tom van der Lee <tom@vanderlee.io>2021-12-15 17:21:49 +0100
commita432fa698692c1e6bce0453f55a988b33385d75d (patch)
tree51bdde6152b51b55527a150e73d4130ce6975ec4 /day15/__init__.py
parent10cca13d8d722bfbae749c285c380233079f65a2 (diff)
download2021-a432fa698692c1e6bce0453f55a988b33385d75d.tar.gz
2021-a432fa698692c1e6bce0453f55a988b33385d75d.tar.bz2
2021-a432fa698692c1e6bce0453f55a988b33385d75d.zip
Day 15
Diffstat (limited to 'day15/__init__.py')
-rw-r--r--day15/__init__.py138
1 files changed, 138 insertions, 0 deletions
diff --git a/day15/__init__.py b/day15/__init__.py
new file mode 100644
index 0000000..38126fc
--- /dev/null
+++ b/day15/__init__.py
@@ -0,0 +1,138 @@
1import math
2from abc import ABC
3from dataclasses import dataclass
4from typing import List, Tuple, Iterator, TypedDict, Dict
5
6from aoc import BaseAssignment
7from aoc.utils import bold
8
9Coordinate = Tuple[int, int]
10Map = List[List[int]]
11
12@dataclass
13class Score:
14 g_score: float
15 h_score: float
16
17 @property
18 def f_score(self):
19 return self.g_score + self.h_score
20
21class Assignment(BaseAssignment, ABC):
22 def parse_item(self, item: str) -> List[int]:
23 return [int(i) for i in item]
24
25 def read_input(self, example = False) -> Map:
26 return list(super().read_input(example))
27
28 @classmethod
29 def neighbours(cls, field: Map, x: int, y: int) -> Iterator[Coordinate]:
30 for ny in list(range(max(0, y - 1), min(len(field) - 1, y + 1) + 1)):
31 if ny == y:
32 continue
33 yield (x, ny)
34
35 for nx in list(range(max(0, x - 1), min(len(field[0]) - 1, x + 1) + 1)):
36 if nx == x:
37 continue
38 yield (nx, y)
39
40 @classmethod
41 def distance(cls, start: Coordinate, end: Coordinate) -> float:
42 start_x, start_y = start
43 end_x, end_y = end
44
45 dx = end_x - start_x
46 dy = end_y - start_y
47
48 return math.sqrt((dx ** 2) + (dy ** 2))
49
50 @classmethod
51 def gen_path(cls, current: Coordinate, came_from: Dict[Coordinate, Coordinate]) -> List[Coordinate]:
52 path = [current]
53 while current in came_from:
54 current = came_from[current]
55 path.append(current)
56 return list(reversed(path))
57
58 @classmethod
59 def a_star(self, field: Map, start: Coordinate, end: Coordinate) -> List[Coordinate]:
60 open = {start}
61 came_from: Dict[Coordinate, Coordinate] = {}
62
63 scores: Dict[Coordinate, Score] = {
64 start: Score(
65 g_score=0,
66 h_score = self.distance(start, end),
67 )
68 }
69
70 while len(open) > 0:
71 current = sorted(open, key=lambda c: scores[c].f_score)[0]
72 if current == end:
73 return self.gen_path(current, came_from)
74
75 open.remove(current)
76 for neighbour in self.neighbours(field, current[0], current[1]):
77 g_score = scores[current].g_score + field[neighbour[1]][neighbour[0]]
78 neighbour_scores = scores.get(neighbour)
79
80 if neighbour_scores is None or g_score < neighbour_scores.g_score:
81 came_from[neighbour] = current
82 neighbour_scores = Score(
83 g_score=g_score,
84 h_score=self.distance(neighbour, end)
85 )
86 scores[neighbour] = neighbour_scores
87
88 if neighbour not in open:
89 open.add(neighbour)
90
91 raise Exception('No Path')
92
93 @classmethod
94 def print_field(cls, field: Map, path: List[Coordinate]):
95 print('', flush=False)
96 for y, row in enumerate(field):
97 print(''.join([ bold(str(i), lambda: (x, y) in path) for x, i in enumerate(row) ]), flush=False)
98 print('', flush=True)
99
100 def run(self, input: Map) -> int:
101 start = (0, 0)
102 end = (len(input[0]) - 1, len(input) - 1)
103
104 path = self.a_star(input, start, end)
105
106 self.print_field(input, path)
107
108 path.remove(start)
109 return sum([
110 input[y][x]
111 for x, y
112 in path
113 ])
114
115class AssignmentOne(Assignment):
116 example_result = 40
117
118class AssignmentTwo(Assignment):
119 example_result = 315
120
121 @classmethod
122 def overflow(self, item):
123 return item - 9 if item > 9 else item
124
125 @classmethod
126 def increase_map(cls, map) -> Map:
127 return [
128 [
129 cls.overflow(item + dx + dy)
130 for dx in range(5)
131 for item in row
132 ]
133 for dy in range(5)
134 for row in map
135 ]
136
137 def read_input(self, example = False) -> Map:
138 return self.increase_map(super().read_input(example))