-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpart2.py
More file actions
128 lines (98 loc) · 4.54 KB
/
Copy pathpart2.py
File metadata and controls
128 lines (98 loc) · 4.54 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
from collections import deque
from typing import List, Set, Tuple, Dict
import heapq
def read_input(filename: str) -> List[str]:
with open(filename, 'r') as f:
return [line.strip() for line in f.readlines()]
def find_start_end(grid: List[str]) -> Tuple[Tuple[int, int], Tuple[int, int]]:
start = end = None
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 'S':
start = (i, j)
elif grid[i][j] == 'E':
end = (i, j)
return start, end
def get_neighbors(pos: Tuple[int, int], grid: List[str], cheating: bool = False) -> List[Tuple[int, int]]:
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
neighbors = []
for dy, dx in directions:
new_y, new_x = pos[0] + dy, pos[1] + dx
if (0 <= new_y < len(grid) and
0 <= new_x < len(grid[0]) and
(cheating or grid[new_y][new_x] != '#')):
neighbors.append((new_y, new_x))
return neighbors
def find_reachable_positions(grid: List[str], start: Tuple[int, int], max_steps: int) -> Dict[Tuple[int, int], int]:
distances = {start: 0}
queue = [(0, start)]
while queue:
dist, current = heapq.heappop(queue)
if dist > max_steps:
continue
for next_pos in get_neighbors(current, grid, cheating=True):
new_dist = dist + 1
if new_dist <= max_steps and (next_pos not in distances or new_dist < distances[next_pos]):
distances[next_pos] = new_dist
heapq.heappush(queue, (new_dist, next_pos))
return distances
def shortest_path(grid: List[str], start: Tuple[int, int], end: Tuple[int, int]) -> Dict[Tuple[int, int], int]:
distances = {}
queue = [(0, start)]
distances[start] = 0
while queue:
dist, current = heapq.heappop(queue)
if dist > distances[current]:
continue
for next_pos in get_neighbors(current, grid):
new_dist = dist + 1
if next_pos not in distances or new_dist < distances[next_pos]:
distances[next_pos] = new_dist
heapq.heappush(queue, (new_dist, next_pos))
return distances
def find_cheats(grid: List[str], normal_distances: Dict[Tuple[int, int], int],
start: Tuple[int, int], end: Tuple[int, int]) -> Dict[int, int]:
MAX_CHEAT_LENGTH = 20
savings = {}
height, width = len(grid), len(grid[0])
# For each possible cheat start position
for y1 in range(height):
for x1 in range(width):
if grid[y1][x1] == '#':
continue
pos1 = (y1, x1)
if pos1 not in normal_distances:
continue
# Find all positions reachable within MAX_CHEAT_LENGTH steps while cheating
reachable = find_reachable_positions(grid, pos1, MAX_CHEAT_LENGTH)
# For each reachable position that's on a valid path
for pos2, cheat_length in reachable.items():
if grid[pos2[0]][pos2[1]] == '#':
continue
# If we can reach both positions in normal path
if pos1 in normal_distances and pos2 in normal_distances:
# Calculate time saved
normal_time = normal_distances[end]
cheat_time = (normal_distances[pos1] +
cheat_length +
(normal_distances[end] - normal_distances[pos2]))
if cheat_time < normal_time:
saved = normal_time - cheat_time
savings[saved] = savings.get(saved, 0) + 1
return savings
def solve(grid: List[str]) -> int:
start, end = find_start_end(grid)
# Replace S and E with . for easier processing
grid = [row.replace('S', '.').replace('E', '.') for row in grid]
# Find normal shortest path distances
normal_distances = shortest_path(grid, start, end)
# Find all possible cheats and their time savings
savings = find_cheats(grid, normal_distances, start, end)
# Count cheats that save at least 100 picoseconds
return sum(count for saved, count in savings.items() if saved >= 100)
def main():
grid = read_input("e:/Advent of Code/Day-20/input.txt")
result = solve(grid)
print(f"Number of cheats saving at least 100 picoseconds: {result}")
if __name__ == "__main__":
main()