1
+ # --- Day 20: Race Condition ---
2
+
3
+ # The Historians are quite pixelated again. This time, a massive,
4
+ # black building looms over you - you're right outside the CPU!
5
+
6
+ # While The Historians get to work, a nearby program sees that you're
7
+ # idle and challenges you to a race. Apparently, you've arrived just
8
+ # in time for the frequently-held race condition festival!
9
+
10
+ # The race takes place on a particularly long and twisting code path;
11
+ # programs compete to see who can finish in the fewest picoseconds.
12
+ # The winner even gets their very own mutex!
13
+
14
+ # They hand you a map of the racetrack (your puzzle input). For example:
15
+
16
+ # ###############
17
+ # #...#...#.....#
18
+ # #.#.#.#.#.###.#
19
+ # #S#...#.#.#...#
20
+ # #######.#.#.###
21
+ # #######.#.#...#
22
+ # #######.#.###.#
23
+ # ###..E#...#...#
24
+ # ###.#######.###
25
+ # #...###...#...#
26
+ # #.#####.#.###.#
27
+ # #.#...#.#.#...#
28
+ # #.#.#.#.#.#.###
29
+ # #...#...#...###
30
+ # ###############
31
+
32
+ # The map consists of track (.) - including the start (S) and end (E)
33
+ # positions (both of which also count as track) - and walls (#).
34
+
35
+ # When a program runs through the racetrack, it starts at the start
36
+ # position. Then, it is allowed to move up, down, left, or right;
37
+ # each such move takes 1 picosecond. The goal is to reach the end
38
+ # position as quickly as possible. In this example racetrack, the
39
+ # fastest time is 84 picoseconds.
40
+
41
+ # Because there is only a single path from the start to the end and the
42
+ # programs all go the same speed, the races used to be pretty boring.
43
+ # To make things more interesting, they introduced a new rule to the
44
+ # races: programs are allowed to cheat.
45
+
46
+ # The rules for cheating are very strict. Exactly once during a race,
47
+ # a program may disable collision for up to 2 picoseconds. This allows
48
+ # the program to pass through walls as if they were regular track. At
49
+ # the end of the cheat, the program must be back on normal track again;
50
+ # otherwise, it will receive a segmentation fault and get disqualified.
51
+
52
+ # So, a program could complete the course in 72 picoseconds (saving 12
53
+ # picoseconds) by cheating for the two moves marked 1 and 2:
54
+
55
+ # ###############
56
+ # #...#...12....#
57
+ # #.#.#.#.#.###.#
58
+ # #S#...#.#.#...#
59
+ # #######.#.#.###
60
+ # #######.#.#...#
61
+ # #######.#.###.#
62
+ # ###..E#...#...#
63
+ # ###.#######.###
64
+ # #...###...#...#
65
+ # #.#####.#.###.#
66
+ # #.#...#.#.#...#
67
+ # #.#.#.#.#.#.###
68
+ # #...#...#...###
69
+ # ###############
70
+
71
+ # Or, a program could complete the course in 64 picoseconds (saving
72
+ # 20 picoseconds) by cheating for the two moves marked 1 and 2:
73
+
74
+ # ###############
75
+ # #...#...#.....#
76
+ # #.#.#.#.#.###.#
77
+ # #S#...#.#.#...#
78
+ # #######.#.#.###
79
+ # #######.#.#...#
80
+ # #######.#.###.#
81
+ # ###..E#...12..#
82
+ # ###.#######.###
83
+ # #...###...#...#
84
+ # #.#####.#.###.#
85
+ # #.#...#.#.#...#
86
+ # #.#.#.#.#.#.###
87
+ # #...#...#...###
88
+ # ###############
89
+
90
+ # This cheat saves 38 picoseconds:
91
+
92
+ # ###############
93
+ # #...#...#.....#
94
+ # #.#.#.#.#.###.#
95
+ # #S#...#.#.#...#
96
+ # #######.#.#.###
97
+ # #######.#.#...#
98
+ # #######.#.###.#
99
+ # ###..E#...#...#
100
+ # ###.####1##.###
101
+ # #...###.2.#...#
102
+ # #.#####.#.###.#
103
+ # #.#...#.#.#...#
104
+ # #.#.#.#.#.#.###
105
+ # #...#...#...###
106
+ # ###############
107
+
108
+ # This cheat saves 64 picoseconds and takes the program
109
+ # directly to the end:
110
+
111
+ # ###############
112
+ # #...#...#.....#
113
+ # #.#.#.#.#.###.#
114
+ # #S#...#.#.#...#
115
+ # #######.#.#.###
116
+ # #######.#.#...#
117
+ # #######.#.###.#
118
+ # ###..21...#...#
119
+ # ###.#######.###
120
+ # #...###...#...#
121
+ # #.#####.#.###.#
122
+ # #.#...#.#.#...#
123
+ # #.#.#.#.#.#.###
124
+ # #...#...#...###
125
+ # ###############
126
+
127
+ # Each cheat has a distinct start position (the position where the cheat
128
+ # is activated, just before the first move that is allowed to go through
129
+ # walls) and end position; cheats are uniquely identified by their start
130
+ # position and end position.
131
+
132
+ # In this example, the total number of cheats (grouped by the amount of
133
+ # time they save) are as follows:
134
+
135
+ # There are 14 cheats that save 2 picoseconds.
136
+ # There are 14 cheats that save 4 picoseconds.
137
+ # There are 2 cheats that save 6 picoseconds.
138
+ # There are 4 cheats that save 8 picoseconds.
139
+ # There are 2 cheats that save 10 picoseconds.
140
+ # There are 3 cheats that save 12 picoseconds.
141
+ # There is one cheat that saves 20 picoseconds.
142
+ # There is one cheat that saves 36 picoseconds.
143
+ # There is one cheat that saves 38 picoseconds.
144
+ # There is one cheat that saves 40 picoseconds.
145
+ # There is one cheat that saves 64 picoseconds.
146
+
147
+ # You aren't sure what the conditions of the racetrack will be like, so
148
+ # to give yourself as many options as possible, you'll need a list of
149
+ # the best cheats. How many cheats would save you at least 100 picoseconds?
150
+
151
+ import numpy as np
152
+ from tqdm import tqdm
153
+ from multiprocessing import Pool
154
+ from time import time
155
+ from numba import jit
156
+ from collections import defaultdict
157
+ from math import ceil , floor , prod
158
+ import sys
159
+ from heapq import heapify , heappush , heappop
160
+
161
+ np .set_printoptions (threshold = np .inf )
162
+ np .set_printoptions (linewidth = np .inf )
163
+
164
+
165
+ def print_mat (m ):
166
+ np .set_printoptions (threshold = np .inf )
167
+ np .set_printoptions (linewidth = np .inf )
168
+ print (np .array2string (m , separator = '' , formatter = {'str_kind' :
169
+ lambda x : x }))
170
+
171
+
172
+ def conv_2d (arr , conv_f ):
173
+ return [[conv_f (a ) for a in line ] for line in arr ]
174
+
175
+
176
+ def flatten (xss ):
177
+ return [x for xs in xss for x in xs ]
178
+
179
+
180
+ def get_neighb (pos , shape ):
181
+ h , w = shape
182
+ i , j = pos
183
+
184
+ ns = []
185
+ if i > 0 :
186
+ ns .append ((i - 1 , j ))
187
+ if i < h - 1 :
188
+ ns .append ((i + 1 , j ))
189
+ if j > 0 :
190
+ ns .append ((i , j - 1 ))
191
+ if j < w - 1 :
192
+ ns .append ((i , j + 1 ))
193
+
194
+ return ns
195
+
196
+
197
+ def dktr2D (visited_dist , start_pos , end_pos = None , blockage = - 1 ):
198
+ # dikstra all paths algorithm for a 2D mat 'visited_dist'
199
+ # 'visited_dist' have its path blocked by a blocking value
200
+ # which cannot be traversed.
201
+ # It is assumed that 'visited_dist' have all unvisited values
202
+ # as max_value and its start position is 0.
203
+ # If end_pos is not none, it will stop when reached
204
+
205
+ # max_dist = sys.maxsize
206
+ shape = visited_dist .shape
207
+ # visited_dist = np.full(shape, max_dist)
208
+ # visited_dist[start_pos] = 0
209
+ unvisited_queue = []
210
+ heappush (unvisited_queue , (visited_dist [start_pos ], start_pos ))
211
+
212
+ # Try to find paths while there are options
213
+ while len (unvisited_queue ) > 0 :
214
+ dist , pos = heappop (unvisited_queue )
215
+
216
+ ns = get_neighb (pos , shape )
217
+ for n in ns :
218
+ new_dist = dist + 1
219
+ if visited_dist [n ] != blockage and new_dist < visited_dist [n ]:
220
+ visited_dist [n ] = new_dist
221
+ heappush (unvisited_queue , (new_dist , n ))
222
+
223
+
224
+ def find_path_from_visited (visited_dist , start_pos , end_pos , blockage = - 1 ):
225
+ pos = end_pos
226
+ path = [end_pos ]
227
+ shape = visited_dist .shape
228
+ while pos != start_pos :
229
+ best_dist = sys .maxsize
230
+ best_next = None
231
+ for n in get_neighb (pos , shape ):
232
+ if visited_dist [n ] != blockage and visited_dist [n ] < best_dist :
233
+ best_dist = visited_dist [n ]
234
+ best_next = n
235
+
236
+ if best_next == None :
237
+ return []
238
+ else :
239
+ path .append (best_next )
240
+ pos = best_next
241
+
242
+ return path
243
+
244
+
245
+ def set_path (m , path , path_val = 99 ):
246
+ m2 = m .copy ()
247
+ h , w = m .shape
248
+ for p in path :
249
+ m2 [p ] = path_val
250
+ for i in range (h ):
251
+ for j in range (w ):
252
+ if m2 [i , j ] > sys .maxsize / 100 :
253
+ m2 [i , j ] = - 10
254
+ return m2
255
+
256
+
257
+ def main ():
258
+ inpt = []
259
+ with open ('input.txt' , 'r' ) as f_in :
260
+ inpt = f_in .readlines ()
261
+
262
+ # Remove \n
263
+ inpt = [i [:- 1 ] for i in inpt ]
264
+
265
+ racetrack = np .array ([list (i ) for i in inpt ])
266
+ h , w = racetrack .shape
267
+
268
+ for i in range (h ):
269
+ for j in range (w ):
270
+ if racetrack [i , j ] == 'S' :
271
+ start_pos = (i , j )
272
+ if racetrack [i , j ] == 'E' :
273
+ end_pos = (i , j )
274
+
275
+ wall_char = '#'
276
+ wall = - 1
277
+ max_dist = sys .maxsize
278
+
279
+ search_space = np .full ((h , w ), max_dist )
280
+ search_space [start_pos ] = 0
281
+
282
+ for i in range (h ):
283
+ for j in range (w ):
284
+ if racetrack [i , j ] == wall_char :
285
+ search_space [i , j ] = wall
286
+
287
+ dktr2D (search_space , start_pos , end_pos = None , blockage = wall )
288
+ new_path = find_path_from_visited (search_space , start_pos , end_pos )
289
+
290
+ path_val = 99
291
+ path_space = set_path (search_space , new_path , path_val )
292
+ print (path_space )
293
+ print (new_path )
294
+ print (len (new_path ) - 1 )
295
+
296
+ # Key: (i,j), value: global improvement
297
+ cheat_list = dict ()
298
+
299
+ def is_horz_cheat (path_space , i , j ):
300
+ return path_space [i , j ] == wall and path_space [
301
+ i , j - 1 ] == path_val and path_space [i , j + 1 ] == path_val
302
+
303
+ def is_vert_cheat (path_space , i , j ):
304
+ return path_space [i , j ] == wall and path_space [
305
+ i - 1 , j ] == path_val and path_space [i + 1 , j ] == path_val
306
+
307
+ # Attempt to find a -1,99,-1 pattern, i.e., a wall
308
+ # Return the one with the highest diff
309
+ cheat_cost = 2
310
+ for i in range (1 , h - 1 ):
311
+ for j in range (1 , w - 1 ):
312
+ can_cheat = False
313
+
314
+ if is_horz_cheat (path_space , i , j ):
315
+ if search_space [i , j - 1 ] < search_space [i , j + 1 ]:
316
+ cheat_start_pos = (i , j - 1 )
317
+ cheat_end_pos = (i , j + 1 )
318
+ else :
319
+ cheat_start_pos = (i , j + 1 )
320
+ cheat_end_pos = (i , j - 1 )
321
+ can_cheat = True
322
+
323
+ if is_vert_cheat (path_space , i , j ):
324
+ if search_space [i - 1 , j ] < search_space [i + 1 , j ]:
325
+ cheat_start_pos = (i - 1 , j )
326
+ cheat_end_pos = (i + 1 , j )
327
+ else :
328
+ cheat_start_pos = (i + 1 , j )
329
+ cheat_end_pos = (i - 1 , j )
330
+ can_cheat = True
331
+
332
+ if can_cheat :
333
+ improv = search_space [cheat_end_pos ] - (
334
+ cheat_cost + search_space [cheat_start_pos ])
335
+ cheat_list [i , j ] = improv
336
+
337
+ print (cheat_list )
338
+ print (sum (np .array (list (cheat_list .values ()))>= 100 ))
339
+
340
+
341
+ if __name__ == '__main__' :
342
+ main ()
0 commit comments