diff options
| -rw-r--r-- | aoc/mixins.py | 57 | ||||
| -rw-r--r-- | day12/__init__.py | 65 | ||||
| -rw-r--r-- | day16/__init__.py | 60 | ||||
| -rw-r--r-- | day16/example.txt | 10 | ||||
| -rw-r--r-- | day16/input.txt | 59 |
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 -*- | ||
| 2 | from math import floor, inf | ||
| 3 | from typing import Tuple, TypeVar, Generic, Callable, Iterator, List, Dict | ||
| 4 | |||
| 5 | Node = TypeVar("Node") | ||
| 6 | |||
| 7 | |||
| 8 | class 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 | |||
| 5 | from typing import List, Tuple, Iterator, Callable, Any | 5 | from typing import List, Tuple, Iterator, Callable, Any |
| 6 | 6 | ||
| 7 | from aoc import BaseAssignment | 7 | from aoc import BaseAssignment |
| 8 | from aoc.mixins import AStarMixin | ||
| 8 | 9 | ||
| 9 | 10 | ||
| 10 | class Map: | 11 | class Map: |
| @@ -57,7 +58,7 @@ class Map: | |||
| 57 | return elevation - 96 | 58 | return elevation - 96 |
| 58 | 59 | ||
| 59 | 60 | ||
| 60 | class Assignment(BaseAssignment, ABC): | 61 | class 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 | |||
| 71 | class 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 | |||
| 120 | class 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 -*- | ||
| 2 | import re | ||
| 3 | from abc import ABC | ||
| 4 | from dataclasses import dataclass | ||
| 5 | from typing import List, Iterator, Dict | ||
| 6 | |||
| 7 | from aoc import BaseAssignment | ||
| 8 | from aoc.mixins import AStarMixin | ||
| 9 | |||
| 10 | |||
| 11 | @dataclass | ||
| 12 | class 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 | |||
| 21 | valve_pattern = re.compile( | ||
| 22 | "Valve (\w+) has flow rate=(\d+); tunnels? leads? to valves? (.*)" | ||
| 23 | ) | ||
| 24 | |||
| 25 | |||
| 26 | class 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 | |||
| 42 | class 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 | |||
| 59 | class AssignmentTwo(Assignment): | ||
| 60 | pass | ||
