2
def isEscapePossible(self, blocked, source, target):4
:type blocked: List[List[int]]5
:type source: List[int]6
:type target: List[int]10
self.blocked = blocked14
# hashset of all blocked cells15
self.blocked_dict = {}17
# hashset of all visited edges20
# min/max x/y coords used for checking if an edge intersects the "easy path"21
self.min_x = min(self.source[0], self.target[0])22
self.max_x = max(self.source[0], self.target[0])23
self.min_y = min(self.source[1], self.target[1])24
self.max_y = max(self.source[1], self.target[1])26
# iterate through all cells in blocked,27
# and add each cell to the hash set28
for blocked_cell in blocked:29
if blocked_cell[0] in self.blocked_dict:30
self.blocked_dict[blocked_cell[0]][blocked_cell[1]] = None32
self.blocked_dict[blocked_cell[0]] = {blocked_cell[1]: None}34
# for each cell in blocked35
for blocked_cell in self.blocked:37
# list the faces of blocked_cell that belong to an outline (4 of these at most)38
exposed_faces = self.enumerate_exposed_faces(39
blocked_cell[0], blocked_cell[1]42
# for each of these faces43
for face in exposed_faces:45
# check to see if we've already visited the edge while tracing out a loop50
x_edge in self.edge_dict51
and y_edge in self.edge_dict[x_edge]52
and edge_type in self.edge_dict[x_edge][y_edge]55
# if not, then we are looking at a new outline that we haven't seen before58
# count the number of edges of the outline that intersect the "easy path"59
num_intervening_edges = self.check_loop(face[0], face[1], face[2])61
# if the number of intersections is odd, a path does not exist. return false62
if num_intervening_edges % 2 == 1:65
# if we could not find an outline the separates source from target, return true68
# lists the faces of occupied cell x,y that do not touch other occupied cells69
# these cell faces are edges that belong to one of the outlines that are formed70
# by painting in all occupied cells and grid boundaries71
# there are at most 4 such edges per cell72
def enumerate_exposed_faces(self, x, y):75
# iterate through each neighbor of the cell78
# if the neighbor cell is not occupied (blocked), then the corresponding face is a boundary face79
# in which case, add it to the list80
if not self.is_occupied(x + self.dxe[i], y + self.dye[i]):82
# there is a little bit of accounting going on to keep track of the correct edge coordinates83
# note that what we are really listing here is a starting grid point (not cell) + starting direction84
# and we also need to take into account that cells are indexed by their bottom left corner85
x_star = x + self.dxe2[i]86
y_star = y + self.dye2[i]87
x_edge_coords = x_star + self.dx_edge_coords[(i + 2) % 4]88
y_edge_coords = y_star + self.dy_edge_coords[(i + 2) % 4]97
self.edge_code_list[i],102
# returns the number of times a given outline intersects the "easy path"103
# x_start,y_start is the starting gridpoint on the outline104
# starting_direction is... the starting direction (see __init__ for how it's coded)105
# is_cc is True if traversing in counterclockwise direction, False if clockwise106
# note that (counter)clockwise is referring to the winding number of the whole outline we are tracing107
def check_loop(self, x_start, y_start, starting_direction, is_cc=True):108
# correct the starting direction if it needs to be adjusted109
starting_direction = self.update_edge_direction(110
x_start, y_start, starting_direction, is_cc112
direction = starting_direction117
num_intervening_edges = 0120
touched_grid_boundary = False122
# iterate until we either touch the grid boundary123
# or return to where we started124
# this is a do-while, hence the True + break condition126
# evaluate next grid point after moving along edge127
x_new = x + self.dxp[direction]128
y_new = y + self.dyp[direction]130
# if the edge is on the boundary, do not proceed. break out of the loop131
if self.edge_on_boundary(x, y, x_new, y_new):132
touched_grid_boundary = True135
# otherwise, mark the edge as visited136
x_edge_coords = x + self.dx_edge_coords[direction]137
y_edge_coords = y + self.dy_edge_coords[direction]138
edge_coords = [x_edge_coords, y_edge_coords]140
# a little bit of a hassle since it's a dictionary of dictionaries of 1-2 element lists141
if x_edge_coords in self.edge_dict:142
if y_edge_coords in self.edge_dict[x_edge_coords]:144
self.edge_code_list[direction]145
not in self.edge_dict[x_edge_coords][y_edge_coords]147
self.edge_dict[x_edge_coords][y_edge_coords].append(148
self.edge_code_list[direction]151
self.edge_dict[x_edge_coords][y_edge_coords] = [152
self.edge_code_list[direction]155
self.edge_dict[x_edge_coords] = {156
y_edge_coords: [self.edge_code_list[direction]]159
# check to see if the edge intersects our "easy path" from source to target160
# if an intersection has occured, increment the intersection counter162
# checks to see if the edge intersects the horizontal portion of the easy path163
# for an edge to do so, it must be vertical, have the same y as source164
# and must have an x between source and target165
if self.edge_code_list[direction] == "v":167
self.min_x < edge_coords[0]168
and edge_coords[0] <= self.max_x169
and edge_coords[1] == self.source[1]172
num_intervening_edges += 1174
# checks to see if the edge interesects the vertical portion of the easy path175
# for an edge to do so, it must be horizontal, have the same x as source176
# and must have a y between source and target177
if self.edge_code_list[direction] == "h":179
self.min_y < edge_coords[1]180
and edge_coords[1] <= self.max_y181
and edge_coords[0] == self.target[0]184
num_intervening_edges += 1186
# update the grid point190
# update the direction191
direction = self.update_edge_direction(x, y, direction, is_cc)193
# if we returned back to the beginning, break out of the loop194
# we need to take direction into account, because it is possible to return to195
# the same grid point during the the middle of the traversal196
# for example, imagine tracing the outline of two cells that are diagonally adjacent197
# here, you would pass through the point where the two cells touch each other twice198
# if you happen to get unlucky and start at such a point, things would break if you199
# terminated conditioned just on the x/y coordinates200
# taking into account the current direction dodges this issue201
if x == x_start and y == y_start and direction == starting_direction:204
# if we touched a grid boundary, we need to go back to the beginning205
# and then go in reverse until we touch the grid boundary again206
# thus finishing the outline.207
# since we are going in reverse direction,208
# the setting is clockwise instead of counter-clockwise209
if touched_grid_boundary and is_cc:210
num_intervening_edges += self.check_loop(211
x_start, y_start, (starting_direction + 2) % 4, is_cc=False214
# return the number of intersections between the loop and the easy path215
return num_intervening_edges217
# changes the edge direction right or left (or keeps straight)218
# depending on our current grid point and direction219
def update_edge_direction(self, x, y, direction, is_cc):220
# coordinate of the cell in front of us (given our current direction) and to the right221
x_right = x + self.dx_fwrd_right[direction]222
y_right = y + self.dy_fwrd_right[direction]224
# coordinate of the cell in front of us (given our current direction) and to the left225
x_left = x + self.dx_fwrd_left[direction]226
y_left = y + self.dy_fwrd_left[direction]228
# the following logic tells us how to change the direction as we move along the outline229
# I'm not going to explain it here, but if you draw the cases out, it will become apparent230
# the one thing to note is that, you can still turn right during a counter-clockwise traversal231
# (and left during a clockwise traversal). The traversal handedness is referring232
# to the global winding number of the whole loop233
# (which is either +1 or -1 depending on clockwise or counter-clockwise)234
# if we are doing a counterclockwise traversal...236
# if the cell in front of us to the right is occupied, turn right237
if self.is_occupied(x_right, y_right):240
# if neither cells in front of us are occupied, turn left241
elif not self.is_occupied(x_left, y_left):245
# if we are doing a clockwise traversal...247
# if the cell in front of us to the left is occupied, turn left248
if self.is_occupied(x_left, y_left):251
# if neither cells in front of us are occupied, turn right252
elif not self.is_occupied(x_right, y_right):257
# checks to see if an edge belongs to the boundary of the grid258
def edge_on_boundary(self, x0, y0, x1, y1):259
# an edge is on the boundary of the grid iff both endpoints260
# are on the boundary of the grid261
b_test0 = x0 == 0 or x0 == self.l or y0 == 0 or y0 == self.l262
b_test1 = x1 == 0 or x1 == self.l or y1 == 0 or y1 == self.l263
return b_test0 and b_test1265
# checks to see if a given cell is blocked. a cell is blocked if either266
# it is on the boundary/exterior of the grid, or it was listed in blocked267
def is_occupied(self, x, y):269
(x in self.blocked_dict and y in self.blocked_dict[x])276
# here is where I hid away all the definitions of constants that we use to make things easier278
# grid dimensions, which we use to check if we hit a grid boundary281
# used to traverse perimeter. clockwise order285
# direction 3 = right286
self.dxp = [0, -1, 0, 1]287
self.dyp = [-1, 0, 1, 0]289
# used to check for faces of a cell that are exposed290
# if the face of a cell is exposed, then that face belongs291
# to a perimeter that may prevent source from reaching target293
# perimeters are traversed in a counterclockwise loop by default294
# specifically, while travelling along a perimenter,295
# the cell to your left will always be occupied and the296
# the cell to your right will always be empty298
# faces of the cell are indexed as:299
# [left, top, right, bottom]300
# if cell x+dxe[i],y+dye[i] if not occupied, then the ith face301
# of the cell is part of a perimeter, which corresponds to motion303
self.dxe = [-1, 0, 1, 0]304
self.dye = [0, 1, 0, -1]306
# used to maps an exposed face of a cell to the corner of the cell307
# used to start the perimeter traversal308
# left face: start at bottom left corner309
# top face: start at top left corner310
# right face: start at top right corner311
# bottom face: start at bottom right corner312
self.dxe2 = [0, 0, 1, 1]313
self.dye2 = [0, 1, 1, 0]315
# used to determine what cells to look at316
# when checking if we need to turn clockwise or counterclockwise317
# while traversing the perimeter318
self.dx_fwrd_right = [-1, -1, 0, 0]319
self.dy_fwrd_right = [-1, 0, 0, -1]321
self.dx_fwrd_left = [0, -1, -1, 0]322
self.dy_fwrd_left = [-1, -1, 0, 0]324
# note that, for the above, a grid point and a cell have the same325
# coordinates if the grid point is the bottom left corner of that cell327
# used to map directed edges to undirected edges328
# we index edges using their bottom/leftmost point, and whether they329
# are vertical or horizontal330
self.edge_code_list = ["v", "h", "v", "h"]331
self.dx_edge_coords = [0, -1, 0, 0]332
self.dy_edge_coords = [-1, 0, 0, 0]