|
2 | 2 |
|
3 | 3 | import numpy |
4 | 4 |
|
5 | | -from . import SeparateRunner |
| 5 | +from . import CombinedRunner |
6 | 6 |
|
7 | 7 | TURNS = ( |
8 | 8 | (-1, 1), |
9 | 9 | (1, -1), |
10 | 10 | ) |
11 | 11 |
|
12 | 12 |
|
13 | | -class DayRunner(SeparateRunner): |
| 13 | +class DayRunner(CombinedRunner): |
14 | 14 | @classmethod |
15 | | - def part1(cls, input: str) -> int: |
| 15 | + def run_both(cls, input: str) -> tuple[int, int]: |
16 | 16 | grid = numpy.array([list(line) for line in input.strip().split("\n")]) |
17 | 17 |
|
18 | 18 | y, x = numpy.where(grid == "S") |
19 | 19 | x, y = x[0], y[0] |
20 | 20 |
|
21 | 21 | todo = [(0, x, y, 1, 0)] |
22 | 22 | best = { |
23 | | - (x, y, 1, 0): 0, |
| 23 | + (x, y, 1, 0): (0, []), |
24 | 24 | } |
25 | 25 |
|
26 | | - def enqueue(dist, x, y, dx, dy): |
| 26 | + def enqueue(dist, x, y, dx, dy, cx, cy, cdx, cdy): |
27 | 27 | if grid[y, x] == "#": |
28 | 28 | return |
29 | 29 |
|
30 | | - if (x, y, dx, dy) not in best or best[x, y, dx, dy] > dist: |
31 | | - best[x, y, dx, dy] = dist |
| 30 | + if (x, y, dx, dy) not in best or best[x, y, dx, dy][0] > dist: |
| 31 | + best[x, y, dx, dy] = (dist, [(cx, cy, cdx, cdy)]) |
32 | 32 | heapq.heappush(todo, (dist, x, y, dx, dy)) |
| 33 | + elif best[x, y, dx, dy][0] == dist: |
| 34 | + best[x, y, dx, dy][1].append((cx, cy, cdx, cdy)) |
| 35 | + |
| 36 | + shortest_dist = None |
| 37 | + finishes = set() |
33 | 38 |
|
34 | 39 | while todo: |
35 | 40 | dist, x, y, dx, dy = heapq.heappop(todo) |
36 | 41 |
|
37 | | - if best[x, y, dx, dy] < dist: |
| 42 | + if best[x, y, dx, dy][0] < dist: |
38 | 43 | continue |
39 | 44 |
|
| 45 | + if shortest_dist is not None and shortest_dist < dist: |
| 46 | + break |
| 47 | + |
40 | 48 | if grid[y, x] == "E": |
41 | | - return dist |
| 49 | + shortest_dist = dist |
| 50 | + finishes.add((x, y, dx, dy)) |
42 | 51 |
|
43 | | - enqueue(dist + 1, x + dx, y + dy, dx, dy) |
44 | | - enqueue(dist + 2001, x - dx, y - dy, dx, dy) |
| 52 | + enqueue(dist + 1, x + dx, y + dy, dx, dy, x, y, dx, dy) |
| 53 | + enqueue(dist + 2001, x - dx, y - dy, dx, dy, x, y, dx, dy) |
45 | 54 |
|
46 | 55 | for tx, ty in TURNS: |
47 | 56 | ndx = dy * ty |
48 | 57 | ndy = dx * ty |
49 | 58 |
|
50 | | - enqueue(dist + 1001, x + ndx, y + ndy, ndx, ndy) |
| 59 | + enqueue(dist + 1001, x + ndx, y + ndy, ndx, ndy, x, y, dx, dy) |
51 | 60 |
|
52 | | - raise ValueError("Did not find path to exit") |
| 61 | + assert shortest_dist is not None, "Should find a path to the exit" |
53 | 62 |
|
54 | | - @classmethod |
55 | | - def part2(cls, input: str) -> int: |
56 | | - pass |
| 63 | + visited_tiles = {(x, y) for x, y, _, _ in finishes} |
| 64 | + todo2 = [f for f in finishes] |
| 65 | + visited_states = set(todo2) |
| 66 | + |
| 67 | + while todo2: |
| 68 | + state = todo2.pop() |
| 69 | + |
| 70 | + for prev in best[state][1]: |
| 71 | + if prev not in visited_states: |
| 72 | + visited_states.add(prev) |
| 73 | + visited_tiles.add((prev[0], prev[1])) |
| 74 | + todo2.append(prev) |
| 75 | + |
| 76 | + return shortest_dist, len(visited_tiles) |
0 commit comments