@@ -54,45 +54,61 @@ def is_free(y, x, maze: Maze):
54
54
return (y , x ) not in maze .walls and y and x and y < maze .size_y and x < maze .size_x
55
55
56
56
57
- def shortest_path (m : Maze , limit = None ) -> int | None :
57
+ def shortest_path (m : Maze , limit = None ) -> tuple [ int | None , list [ tuple [ int , int ]]] :
58
58
distances = {
59
59
(y , x ): 0 if (y , x ) == m .start else sys .maxsize
60
60
for y in range (m .size_y )
61
61
for x in range (m .size_x )
62
62
if is_free (y , x , m )
63
63
}
64
-
64
+ predecessor : dict [ tuple [ int , int ], tuple [ int , int ]] = dict ()
65
65
while distances :
66
66
min_pos , ucost = min (distances .items (), key = itemgetter (1 ))
67
67
del distances [min_pos ]
68
68
if limit and ucost > limit :
69
- return
69
+ return None , []
70
70
if min_pos == m .end :
71
- return ucost
71
+ pos = min_pos
72
+ route = [pos ]
73
+ while True :
74
+ pos = predecessor [pos ]
75
+ route = [pos , * route ]
76
+ if pos == m .start :
77
+ return ucost , route
78
+
72
79
for pos in [(min_pos [0 ] + d [0 ], min_pos [1 ] + d [1 ]) for d in [(- 1 , 0 ), (1 , 0 ), (0 , - 1 ), (0 , 1 )]]:
73
80
if (d := distances .get (pos )) and d > ucost + 1 :
74
81
distances [pos ] = ucost + 1
82
+ predecessor [pos ] = min_pos
83
+
84
+
85
+ def manhattan_distance (pos1 : tuple [int , int ], pos2 : tuple [int , int ]):
86
+ return abs (pos1 [0 ] - pos2 [0 ]) + abs (pos1 [1 ] - pos2 [1 ])
87
+
88
+
89
+ def cheat_paths (path , max_cheat_points = 2 ):
90
+ for i in range (len (path )):
91
+ for j in range (i , len (path )):
92
+ if (d := manhattan_distance (path [i ], path [j ])) <= max_cheat_points :
93
+ yield (i + len (path ) - j + d - 1 )
75
94
76
95
77
96
def part1 (orig_maze : Maze ):
78
- non_cheat_score = shortest_path (orig_maze )
97
+ non_cheat_score , path = shortest_path (orig_maze )
98
+ assert non_cheat_score
99
+ return len ([score for score in cheat_paths (path ) if score <= non_cheat_score - 100 ])
100
+
101
+
102
+ def part2 (orig_maze : Maze ):
103
+ non_cheat_score , path = shortest_path (orig_maze )
79
104
assert non_cheat_score
80
- count = 0
81
- to_check = len (orig_maze .walls )
82
- c = 0
83
- for remove_pos in orig_maze .walls :
84
- walls = orig_maze .walls - {remove_pos }
85
- m = dataclasses .replace (orig_maze , walls = walls )
86
- if shortest_path (m , non_cheat_score - 100 ):
87
- count += 1
88
- c += 1
89
- print ("Done:" , 100 * c / to_check )
90
- return count
105
+ return len ([score for score in cheat_paths (path , 20 ) if score <= non_cheat_score - 100 ])
91
106
92
107
93
108
def main ():
94
109
m = parse_maze (Path (sys .argv [1 ]).read_text ())
95
110
print (f"Part1: { part1 (m )} " )
111
+ print (f"Part2: { part2 (m )} " )
96
112
97
113
98
114
if __name__ == "__main__" :
0 commit comments