Google Foobar Challenge: "Prepare the Bunnies' Escape" - Dijkstra's Algorithm Implementation - search

I'm having a go at the Google Foobar Challenge, currently on the Prepare the Bunnies' Escape problem.
Problem definition:
You're awfully close to destroying the LAMBCHOP doomsday device and
freeing Commander Lambda's bunny workers, but once they're free of the
work duties the bunnies are going to need to escape Lambda's space
station via the escape pods as quickly as possible. Unfortunately, the
halls of the space station are a maze of corridors and dead ends that
will be a deathtrap for the escaping bunnies. Fortunately, Commander
Lambda has put you in charge of a remodeling project that will give
you the opportunity to make things a little easier for the bunnies.
Unfortunately (again), you can't just remove all obstacles between the
bunnies and the escape pods - at most you can remove one wall per
escape pod path, both to maintain structural integrity of the station
and to avoid arousing Commander Lambda's suspicions.
You have maps of parts of the space station, each starting at a work
area exit and ending at the door to an escape pod. The map is
represented as a matrix of 0s and 1s, where 0s are passable space and
1s are impassable walls. The door out of the station is at the top
left (0,0) and the door into an escape pod is at the bottom right
(w-1,h-1).
Write a function solution(map) that generates the length of the
shortest path from the station door to the escape pod, where you are
allowed to remove one wall as part of your remodeling plans. The path
length is the total number of nodes you pass through, counting both
the entrance and exit nodes. The starting and ending positions are
always passable (0). The map will always be solvable, though you may
or may not need to remove a wall. The height and width of the map can
be from 2 to 20. Moves can only be made in cardinal directions; no
diagonal moves are allowed.
I thought I would have a go at solving the problem using Dijkstra's algorithm and managed to get all the test cases passing except for one hidden one.
Fortunately I have been able to come up with a test case that can replicate the situation.
My implementation is below:
#!/usr/bin/env python2.7
from typing import List, Tuple, Dict, Optional
from Queue import PriorityQueue
def neighbors(map, coord):
# type: (List[List[int]], Tuple[int, int]) -> List[Tuple[int, int]]
adjacent = []
if coord[0] > 0:
adjacent.append((coord[0] - 1, coord[1]))
if coord[0] < len(map) - 1:
adjacent.append((coord[0] + 1, coord[1]))
if coord[1] > 0:
adjacent.append((coord[0], coord[1] - 1))
if coord[1] < len(map[coord[0]]) - 1:
adjacent.append((coord[0], coord[1] + 1))
return adjacent
def heuristic(a, b):
# type: (Tuple[int, int], Tuple[int, int]) -> float
# Manhattan Distance heuristic
return abs(b[0] - a[0]) + abs(b[1] - a[1])
def reconstruct_path(came_from, current):
# type: (Tuple[int, int], Tuple[int, int]) -> List[Tuple[int, int]]
# Backtrack from 'current' back up to the start
path = [current]
while current in came_from:
current = came_from[current]
if current is not None:
path.insert(0, current)
return path
def dijkstras_algo(map, start, end, num_removable_walls):
# type: (List[List[int]], Tuple[int, int], Tuple[int, int], int) -> int
open = PriorityQueue()
open.put((0, start))
# num_removable_walls[n] is the remaining number of walls we could remove when we landed on the square
num_removable_walls = { start: num_removable_walls }
# came_from[n] is the node immediately preceding it on the cheapest path from 'start'
came_from = { start: None } # type: Dict[Tuple[int, int], Optional[Tuple[int, int]]]
# cost_so_far[n] is the cost to travel from 'start' to n
cost_so_far = { start: 0 } # type: Dict[Tuple[int, int], int]
while not open.empty():
(cost, current) = open.get()
if current == end:
return reconstruct_path(came_from, current)
for next in neighbors(map, current):
if map[next[0]][next[1]] == 1 and num_removable_walls[current] <= 0:
continue
# For this problem, distance between any two neighbouring cells is _always_ 1
new_cost = cost_so_far[current] + 1
# The problem is in this check, for the failing test case, we stop revisiting cells
if (next not in cost_so_far or new_cost <= cost_so_far[next]):
num_removable_walls[next] = num_removable_walls[current] - (1 if map[next[0]][next[1]] == 1 else 0)
cost_so_far[next] = new_cost
open.put((new_cost + heuristic(next, end), next))
came_from[next] = current
def solution(map):
# type: (List[List[int]]) -> int
num_rows = len(map)
num_cols = len(map[0])
start = (0, 0)
end = (num_rows - 1, len(map[num_rows - 1]) - 1)
path = dijkstras_algo(map, start, end, 1)
return None if path == None else len(path)
assert solution([[0, 1, 0, 0, 0, 1],
[0, 0, 0, 1, 0, 0]]) == 7, '2x6 failed!'
assert solution([[0, 1, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 1, 1],
[0, 0, 1, 1, 0]]) == 12, '4x5 failed!'
assert solution([[0, 1, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 1, 1, 0],
[0, 1, 1, 0, 0],
[0, 1, 1, 0, 0]]) == 9, '5x5 failed!'
Running the code above will show the 4x5 array test failing.
I have ascertained that it is because the algorithm is not revisiting cells to continue searching. This is because of the if-statement if (next not in cost_so_far or new_cost <= cost_so_far[next]):. The algorithm initially finds a low-cost route to the wall cells at (0, 1) and (1, 1), and uses up it's one-time ability to remove a wall on those squares immediately. This cost is then recorded and because it is low, the aforementioned check in the algorithm will not revisit the surrounding cells as a result - as it has found the shortest path to that cell. This is a problem because we can see that we need to save our one-time ability to remove a wall until we encounter cell (2, 4) in order to reach the end.
Although I know where the problem lies, I am having a hard time coming up with a way to incorporate exploring the map while taking the wall-removing ability into account. Any ideas would be greatly appreciated.
I successfully implemented Dijkstra's algorithm to explore the grid, not passing through walls as per the vanilla implementation. However I haven't been successful incorporating the possibility of finding the shortest path, given that you can remove one wall from the map/maze.

Related

Why shortest path not always found for Leetcode 1129 challenge?

I am trying to solve LeetCode problem 1129. Shortest Path with Alternating Colors:
You are given an integer n, the number of nodes in a directed graph where the nodes are labeled from 0 to n - 1. Each edge is red or blue in this graph, and there could be self-edges and parallel edges.
You are given two arrays redEdges and blueEdges where:
redEdges[i] = [aᵢ, bᵢ] indicates that there is a directed red edge from node aᵢ to node bᵢ in the graph, and
blueEdges[j] = [uⱼ, vⱼ] indicates that there is a directed blue edge from node uⱼ to node vⱼ in the graph.
Return an array answer of length n, where each answer[x] is the length of the shortest path from node 0 to node x such that the edge colors alternate along the path, or -1 if such a path does not exist.
Example 1:
Input: n = 3, redEdges = [[0,1],[1,2]], blueEdges = []
Output: [0,1,-1]
Here is my code for that problem:
class Solution:
def shortestAlternatingPaths(self, n: int, redEdges: List[List[int]], blueEdges: List[List[int]]) -> List[int]:
res = [0] + [-1]*(n-1)
Red = defaultdict(list)
Blue = defaultdict(list)
for x,y in redEdges:
if x!=0 or y!=0:
Red[x].append(y)
for x,y in blueEdges:
if x!=0 or y!=0:
Blue[x].append(y)
def dfs(vertex,color,cost):
if color == "red":
for x in Red[vertex]:
if res[x] != -1:
res[x] = min(cost,res[x])
else:
res[x] = cost
if vertex in Red.keys():
del Red[vertex]
dfs(x,"blue",cost+1)
else:
for x in Blue[vertex]:
if res[x] != -1:
res[x] = min(cost,res[x])
else:
res[x] = cost
if vertex in Blue.keys():
del Blue[vertex]
dfs(x,"red",cost+1)
dfs(0,"red",1)
dfs(0,"blue",1)
return res
But for the following input:
redEdges=[[2,2],[0,1],[0,3],[0,0],[0,4],[2,1],[2,0],[1,4],[3,4]]
blueEdges=[[1,3],[0,0],[0,3],[4,2],[1,0]]
...my output is:
[0,1,4,1,1]
But the correct solution is:
[0,1,2,1,1]
...because there is a path from node 0 to node 2 like this:
red blue
0 -----> 4 -----> 2
I have no idea why my code doesn't give 2 as this path should be found via my DFS algorithm.
I thought that it might be something with the [0,0] edge, but it seems that it doesn't have an impact on a solution.
What is wrong in my code?
The problem is that your code deletes the vertex it has visited, but doesn't restore it when backtracking. There is a possibility that there is another path from vertex 0 to the one you just deleted that still needs to be traversed and is a shorter path.
Here is example input that demonstrates the problem:
redEdges = [[0,1],[2,3],[0,3]]
blueEdges = [[1,2],[3,4]]
Your code will correctly create the following adjacency lists:
Red = {0: [1, 3], 2: [3]}
Blue = {1: [2], 3: [4]}
With dfs the path 0, 1, 2, 3, 4 will be visited and during this traversal all the keys in these dictionaries will be deleted. As the loop over the outgoing edges from 0 is still active, dfs will still follow the red edge from 0 to 3, but there it finds no blue edges as the key 3 is not there anymore. And so the algorithm doesn't see the shorter path from 0 to 4, which is 0, 3, 4.
Not your question, but BFS is more suitable for finding shortest paths. I would suggest you rework the algorithm and use BFS instead.
Just to have a complete answer, here is a spoiler solution using BFS:
class Solution:
def shortestAlternatingPaths(self, n, redEdges, blueEdges):
# Build a separate adjacency list for each color
adj = [[[] for _ in range(n)], [[] for _ in range(n)]]
for i, edges in enumerate((redEdges, blueEdges)):
for x, y in edges:
if x or y:
adj[i][x].append(y)
# Collect shortest distances for each color separately
res = [[0] + [-1] * (n-1), [0] + [-1] * (n-1)]
# Start BFS at node 0, traversing with either color
frontier = [(0, 0), (0, 1)]
distance = 1
while frontier: # BFS loop
nextfrontier = []
for node, color in frontier:
for neighbor in adj[color][node]:
# If not yet visited with this color...
if res[color][neighbor] == -1:
res[color][neighbor] = distance
nextfrontier.append((neighbor, 1-color))
frontier = nextfrontier
distance += 1
# Get the minimum distance per node from the two color alternatives
return [min(a, b) if min(a, b) > -1 else max(a, b)
for a, b in zip(*res)]

what is wrong with my code? leetcode - 189. Rotate Array

The code works perfectly fine for the first test case but gives wrong answer for the second one. Why is that?
arr = [1,2,3,4,5,6,7]
arr2 = [-1,-100,3,99]
def reverse(array, start, end):
while start < end:
array[start], array[end] = array[end], array[start]
start += 1
end -= 1
return array
def rotate(array, k):
reverse(array, 0, k)
reverse(array, k+1, len(array)-1)
reverse(array, 0, len(array)-1)
return array
print(rotate(arr, 3)) # output: [5, 6, 7, 1, 2, 3, 4]
# print(reverse(arr, 2, 4))
rotate(arr2, 2)
print(arr2) # output: [99, -1, -100, 3] (should be [3, 99, -1, -100])
Your existing logic does the following -
Move k + 1 item from front of the list to the back of the list.
But the solution needs to move k elements from back of the list to the front. Or another way to think is move len(array) - k element from front to the back.
To do so, two changes required in the rotate() -
Update k to len(array) - k
Change your logic to move k instead of k + 1 element from front to back
So, your rotate() needs to be changed to following -
def rotate(array, k):
k = len(array) - k
reverse(array, 0, k-1)
reverse(array, k, len(array)-1)
reverse(array, 0, len(array)-1)
return array
I think there are better ways to solve this but with your logic this should solve the problem.

Coin Change problem using Memoization (Amazon interview question)

def rec_coin_dynam(target,coins,known_results):
'''
INPUT: This funciton takes in a target amount and a list of possible coins to use.
It also takes a third parameter, known_results, indicating previously calculated results.
The known_results parameter shoud be started with [0] * (target+1)
OUTPUT: Minimum number of coins needed to make the target.
'''
# Default output to target
min_coins = target
# Base Case
if target in coins:
known_results[target] = 1
return 1
# Return a known result if it happens to be greater than 1
elif known_results[target] > 0:
return known_results[target]
else:
# for every coin value that is <= than target
for i in [c for c in coins if c <= target]:
# Recursive call, note how we include the known results!
num_coins = 1 + rec_coin_dynam(target-i,coins,known_results)
# Reset Minimum if we have a new minimum
if num_coins < min_coins:
min_coins = num_coins
# Reset the known result
known_results[target] = min_coins
return min_coins
This runs perfectly fine but I have few questions about it.
We give it the following input to run:
target = 74
coins = [1,5,10,25]
known_results = [0]*(target+1)
rec_coin_dynam(target,coins,known_results)
why are we initalising the know result with zeros of length target+1? why can't we just write
know_results = []
Notice that the code contains lines such as:
known_results[target] = 1
return known_results[target]
known_results[target] = min_coins
Now, let me demonstrate the difference between [] and [0]*something in the python interactive shell:
>>> a = []
>>> b = [0]*10
>>> a
[]
>>> b
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
>>>
>>> a[3] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list assignment index out of range
>>>
>>> b[3] = 1
>>>
>>> a
[]
>>> b
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
The exception IndexError: list assignment index out of range was raised because we tried to access cell 3 of list a, but a has size 0; there is no cell 3. We could put a value in a using a.append(1), but then the 1 would be at position 0, not at position 3.
There was no exception when we accessed cell 3 of list b, because b has size 10, so any index between 0 and 9 is valid.
Conclusion: if you know in advance the size that your array will have, and this size never changes during the execution of the algorithm, then you might as well begin with an array of the appropriate size, rather than with an empty array.
What is the size of known_results? The algorithm needs results for values ranging from 0 to target. How many results is that? Exactly target+1. For instance, if target = 2, then the algorithm will deal with results for 0, 1 and 2; that's 3 different results. Thus known_results must have size target+1. Note that in python, just like in almost every other programming language, a list of size n has n elements, indexed 0 to n-1. In general, in an integer interval [a, b], there are b-a+1 integers. For instance, there are three integers in interval [8, 10] (those are 8, 9 and 10).

Calculate transformation based on anchor in image opencv2 / py

I would like to calculate transformation matrix (rotation, scaling and translation) according to an anchor in an image.
My image is a picture of a label, which will always contains a datamatrix.
I use a third-party library to detect datamatrix.
Then, I get its size, orientation (using the result of cv2.minAreaRect(dm_contour)), and position.
I build what I call my "anchor" with those parameters.
In a second step I get what I call a job, which is composed of ROIs defined by user and the anchor of the picture on which the user defined the ROI.
With these few steps I can correctly place my ROIs according to new label context if it has only a translfation (shifted to left, right, top, bottom).
But as soon as I try to replace ROIs on a rotated label, it doesn't work.
If think my issue is with my rotation matrix and the whole "translate to origen and back to position" process. But I can't find what I m doing wrong...
My code to transform ROIs position looks like that :
def process_job(anchor, img, job, file_path):
"""
Process job file on current picture
#param anchor = Current scene anchor
#param img = Current picture
#param job = Job object
#param file_path = Job file path
"""
print("Processing job " + file_path)
""" Unpack detected anchor """
a_x, a_y = (anchor[0], anchor[1])
rotation = anchor[2]
anchor_size = int(anchor[3])
for item_i in job:
item = job[item_i]
if 'anchor' in item:
""" Apply size rate """
size_rate = anchor_size / int(item['anchor']['size'])
"""" Item anchor pos """
i_a_x, i_a_y = int(item['anchor']['x']), int(item['anchor']['y'])
""" Calculate transformation """
""" Scaling """
S = np.array([
[size_rate, 0, 0],
[ 0, size_rate, 0],
[ 0, 0, 1]
])
""" Rotation """
angle = rotation - int(item['anchor']['o'])
theta = np.radians(angle)
c, s = np.cos(theta), np.sin(theta)
R = np.array((
(c, s, 0),
(-s, c, 0),
(0, 0, 1)
))
""" Translation """
x_scale = a_x - i_a_x
y_scale = a_y - i_a_y
T = np.array([
[1, 0, x_scale],
[0, 1, y_scale],
[0, 0, 1]
])
""" Shear """
shx_factor = 0
Shx = np.array([
[1, shx_factor, 0],
[0, 1, 0],
[0, 0, 1]
])
shy_factor = 0
Shy = np.array([
[1,0, 0],
[shy_factor, 1, 0],
[0, 0, 1]
])
print("Scaling: " + str(size_rate) + " Rotation:" + str(angle) + " Translation:" + str((x_scale, y_scale)))
if 'rect' in item:
""" Unpack rectangle """
""" (r_x1, r_y1) top-left corner """
""" (r_x2, r_y2) bottom right corner """
r_x1, r_y1, r_x2, r_y2 = (int(item['rect']['x1']), int(item['rect']['y1']), int(item['rect']['x2']), int(item['rect']['y2']))
""" As np arrays """
rect_1 = np.array([r_x1, r_y1, 1])
rect_2 = np.array([r_x2, r_y2, 1])
""" Translate to origen """
T_c_1 = np.array([
[1, 0, -r_x1],
[0, 1, -r_y1],
[0, 0, 1]
])
""" Translate to origen """
T_c_2 = np.array([
[1, 0, -r_x2],
[0, 1, -r_y2],
[0, 0, 1]
])
""" Back to postion """
T_r1 = np.array([
[1, 0, r_x1],
[0, 1, r_y1],
[0, 0, 1]
])
""" Back to postion """
T_r2 = np.array([
[1, 0, r_x2],
[0, 1, r_y2],
[0, 0, 1]
])
""" Apply transformations """
final_1 = T # T_r1 # R # T_c_1 # S # rect_1
final_2 = T # T_r2 # R # T_c_2 # S # rect_2
x1, y1, x2, y2 = final_1[0], final_1[1], final_2[0], final_2[1]
print("From " + str((r_x1, r_y1, r_x2, r_y2)))
print("To " + str((int(x1), int(y1), int(x2), int(y2))))
cv2.line(img, (int(x1), int(y1)), (int(x2), int(y2)), \
(0,0,0), 2)
cv2.imwrite('./output/job.png', img)
And here a fex sample of my images :
Thanks in advance for your help,
So,
I don't even know if someone took the time to read my question, but if it can be of any help, here is what I did.
In my first code version, I tried to calculate the following transformation matrix:
Translation matrix 'T'
Rotation 'R'
Scaling 'S'
But was missing two of them:
Sheer X 'ShX'
Sheer Y 'ShY'
My first second version looked like roi_pos = ShX # ShY # S # T # T_to_pos # R # T_to_origin # item_roi
Results were very clumsy and the ROI I difined with my model were not correctly located on my test samples. But rotation was right and somehow ROIs would fall near the expected results.
Then I thought about optimizing my Datamatrix detection, so I went throught all the trouble to implement my own python/numpy/openCV version of a DM detection algorithm.
A sharped DM detection helped me evaluate better my orientation and scale parameter but ROIs were still off.
So I discovered homography, which exactly do what I want.
Its takes points in a known plan and same points in a destination plan. It then calculate the transformation that occured between the two plans.
With this matrix 'H', I know can do roi_pos = H # item_roi which is much more accurate.
That's it, hope it helps,

Why in this recursive function in Python does the execution continue even after the base condition is met?

The Following is a code snippet of a game called Ink Spill from the book 'Make Games with Python & Pygame' by Al Sweigart. (Full Code here: http://invpy.com/inkspill.py)
This recursive function is responsible for changing
the old color ( in this case '2', which is mainBoard[0][0]) to the new color (in this case '3', which is mainBoard[1][0])
that the player clicks on .
My question is: In this example why even when the base condition is
met and the return is executed, the execution still jumps to the next block inside the function.
I have tested this with print statements all over the function many times and with different parameters and also on Visualize Code website ...I still don't understand it!
Here, I have deleted many of my print statements and have left just two at the beginning. If you run this code you will see on your console that on the third line the condition is met (3 !=2), but the execution continues!
I really appreciate any help. Thank you very much.
And by the way unfortunately, the other question that was asked by someone else: Why does this recursive function continue even after its base case has been satisfied didn't answer my question although very similar!
boardWidth = 3
boardHeight = 3
mainBoard = [[2, 0, 0], [3, 0, 0], [4, 0, 0]]
# (blue, red, red),(yellow, red, red),(orange, red, red)
def floodFill(board, oldColor, newColor, x, y):
print(board[x][y], oldColor)
print(mainBoard)
if board[x][y] != oldColor:
return
board[x][y] = newColor # change the color of the current box
# Make the recursive call for any neighboring boxes:
if x > 0:
floodFill(board, oldColor, newColor, x - 1, y)
if x < boardWidth - 1:
floodFill(board, oldColor, newColor, x + 1, y)
if y > 0:
floodFill(board, oldColor, newColor, x, y - 1)
if y < boardHeight - 1:
floodFill(board, oldColor, newColor, x, y + 1)
floodFill(mainBoard, 2, 3, 0, 0)

Resources