diff options
| author | 2021-12-15 17:20:46 +0100 | |
|---|---|---|
| committer | 2021-12-15 17:21:49 +0100 | |
| commit | a432fa698692c1e6bce0453f55a988b33385d75d (patch) | |
| tree | 51bdde6152b51b55527a150e73d4130ce6975ec4 /day15/__init__.py | |
| parent | 10cca13d8d722bfbae749c285c380233079f65a2 (diff) | |
| download | 2021-a432fa698692c1e6bce0453f55a988b33385d75d.tar.gz 2021-a432fa698692c1e6bce0453f55a988b33385d75d.tar.bz2 2021-a432fa698692c1e6bce0453f55a988b33385d75d.zip | |
Day 15
Diffstat (limited to 'day15/__init__.py')
| -rw-r--r-- | day15/__init__.py | 138 |
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 @@ | |||
| 1 | import math | ||
| 2 | from abc import ABC | ||
| 3 | from dataclasses import dataclass | ||
| 4 | from typing import List, Tuple, Iterator, TypedDict, Dict | ||
| 5 | |||
| 6 | from aoc import BaseAssignment | ||
| 7 | from aoc.utils import bold | ||
| 8 | |||
| 9 | Coordinate = Tuple[int, int] | ||
| 10 | Map = List[List[int]] | ||
| 11 | |||
| 12 | @dataclass | ||
| 13 | class 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 | |||
| 21 | class 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 | |||
| 115 | class AssignmentOne(Assignment): | ||
| 116 | example_result = 40 | ||
| 117 | |||
| 118 | class 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)) | ||
