-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path__init__.py
More file actions
138 lines (103 loc) · 3.6 KB
/
__init__.py
File metadata and controls
138 lines (103 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations
from dataclasses import dataclass
from math import inf, sqrt
from typing import *
from aocpy import BaseChallenge, Vector
CARDINAL_DIRECTIONS = ((0, 1), (0, -1), (1, 0), (-1, 0))
@dataclass
class Node:
pos: Vector
value: int
edges: List[Node]
def __init__(
self,
pos: Vector,
value: int = 0,
edges: Optional[List[Tuple[Node, int]]] = None,
):
self.pos = pos
self.value = value
self.edges = [] if edges is None else edges
def parse(instr: str) -> Tuple[Dict[Vector, Node], Vector, Vector]:
start: Vector
end: Vector
nodes: Dict[Vector, Node] = {}
for y, line in enumerate(instr.strip().splitlines()):
for x, char in enumerate(line):
conv_char = char
if char == "S":
conv_char = "a"
elif char == "E":
conv_char = "z"
v = Vector(x, y)
height = ord(conv_char) - ord("a")
n = Node(v, height)
nodes[v] = n
if char == "S":
start = n.pos
elif char == "E":
end = n.pos
for coord in nodes:
n = nodes[coord]
for modifier in CARDINAL_DIRECTIONS:
check = coord + modifier
adjacent_node = nodes.get(check)
if adjacent_node is not None:
if adjacent_node.value - n.value < 2:
n.edges.append(adjacent_node)
return nodes, start, end
def shortest_path(nodes: Dict[Vector, Node], begin: Vector, end: Vector) -> int:
priorities: Dict[Vector, Tuple[Union[int, float], Optional[Vector]]] = {
node: (inf, None) for node in nodes
}
visited: Dict[Vector, None] = {begin: None}
cursor = begin
while True:
if cursor == end:
break
n = nodes[cursor]
visited[cursor] = None
length_to_current = priorities[cursor][0]
if length_to_current == inf:
length_to_current = 0
# every neighbour that's not been visited already
for neighbour in n.edges:
if neighbour.pos in visited:
continue
if priorities[neighbour.pos][0] > length_to_current + 1:
priorities[neighbour.pos] = (length_to_current + 1, cursor)
# work out next item
min_priority = (inf, None)
for node_name in priorities:
if node_name not in visited and priorities[node_name][0] < min_priority[0]:
min_priority = (priorities[node_name][0], node_name)
cursor = min_priority[1]
route: List[str] = []
while priorities[cursor][1] is not None:
route.insert(0, cursor)
cursor = priorities[cursor][1]
return len(route)
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
nodes, start, end = parse(instr)
return shortest_path(nodes, start, end)
@staticmethod
def two(instr: str) -> int:
nodes, _, end = parse(instr)
# possible starting positions are ones with value=0 bordering one with value=1
starting_positions: List[Vector] = []
for coord in nodes:
node = nodes[coord]
if node.value != 0:
continue
for adj_node in node.edges:
if adj_node.value == 1:
starting_positions.append(coord)
break
shortest = inf
for coord in starting_positions:
x = shortest_path(nodes, coord, end)
if x < shortest:
shortest = x
return shortest