Why shortest path not always found for Leetcode 1129 challenge? - python-3.x

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)]

Related

Parking problem -pickup and delivery with ordering

I am trying to solve an optimization problem, using the CP-Sat solver of Ortools, that is very similar to a standard Pickup and Delivery example but with a twist that I cannot figure out how to solve. A simplified version of my problem would be:
I have some containers that needs to be picked up, by a single vehicle, and moved to another area with pre-allocated "parking-lots". The task is to find the minimal travel distance of the vehicle. Each pickup can be dropped off on any of the parking lots in the other area.
I have so far managed to solve this by utilizing AddCircuit, on a graph with a start node with edges going out to all pickup nodes, and with each pickup node having edges going out to all parking nodes and modelling the possible edges as booleans.
Example: start node (0), two pickups (1,2) and two parking lots (3,4). See figure.
A twist to the problem that I cannot figure out how solve, is that there should be an ordering of the parkings. Imagine the parking lot is a tunnel with one open end. I.e. I cannot park at the bottom of the tunnel if a container has already been parked in beginning of the tunnel. For the example attached, I cannot park a container on node 4 if node 3 has already had a container parked. In this case the attached minimal path solution (0->1->3->2->4) is not valid but should rather be 0->2->4->1->3->0 (also being the only feasible path for this simple example).
I would appreciate any help I could get on this.
A solution to the example without the ordering of the parkings are shown below:
import numpy as np
from ortools.sat.python import cp_model
model = cp_model.CpModel()
solver = cp_model.CpSolver()
start_node = (0, 0)
pickup_nodes = [(-1, 1), (1, 3)]
drop_off_nodes = [(-1, 2), (1, 4)]
all_nodes = [start_node] + pickup_nodes + drop_off_nodes
pickup_indices = [1, 2]
drop_off_indices = [3, 4]
scale = 100 # scale to solve rounding problem
# using euclidean distance
distances = [
[int(scale * np.sqrt((n1[0] - n2[0]) ** 2 + (n1[1] - n2[1]) ** 2)) for n2 in all_nodes]
for n1 in all_nodes
]
literals = {}
all_arcs = []
# start-> pickup
for i in pickup_indices:
literals[0, i] = model.NewBoolVar(f"{0} -> {i}") # start arc
all_arcs.append((0, i, literals[0, i]))
# pickup -> drop off
for i in pickup_indices:
for j in drop_off_indices:
literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
all_arcs.append((i, j, literals[i, j]))
# drop off -> pickup
for i in drop_off_indices:
for j in pickup_indices:
literals[i, j] = model.NewBoolVar(f"{i} -> {j}")
all_arcs.append((i, j, literals[i, j]))
# drop off -> start
for i in drop_off_indices:
literals[i, 0] = model.NewBoolVar(f"{i} -> {0}")
all_arcs.append((i, 0, literals[i, 0]))
model.AddCircuit(all_arcs)
model.Minimize(sum(literals[i, j] * distances[i][j] for i, j in literals))
solver.Solve(model)
print(f"Travel distance: {solver.ObjectiveValue()}")
# print path
start_node = 0
node_idx = 0
print(node_idx, end="")
full_circuit = False
while not full_circuit:
for i, pos in enumerate(all_nodes):
if (node_idx, i) in literals and solver.Value(literals[(node_idx, i)]):
print(f" -> {i}", end="")
node_idx = i
break
if node_idx == start_node:
full_circuit = True
N.B: This question has also been posted on https://groups.google.com/g/or-tools-discuss/c/xazcgayBUok

Rotate k-partite graph in Network

I would like to rotate the following k-partite graph vertically or at 45 degrees. I want to show the following plot in a hierarchical way where red nodes are at the top and green node-set are at the bottom
Documentation of networks only have a rotate option for shell_layout and for edges labels networkx.drawing.nx_pylab.draw_networkx_edge_labels
Here is the program code:
G = nx.Graph()
G.add_nodes_from(emc["entity"], bipartite=0)
G.add_nodes_from(set(EMM_unique["keys"]).symmetric_difference(set(emc["entity"])), bipartite=1)
G.add_nodes_from(EMM["id"], bipartite=2)
G.add_edges_from(list(emc.itertuples(index=False)))
G.add_edges_from(list(EMM.itertuples(index=False)))
nodes = G.nodes()
# for each of the parts create a set
nodes_0 = set([n for n in nodes if G.nodes[n]['bipartite']==0])
nodes_1 = set([n for n in nodes if G.nodes[n]['bipartite']==1])
nodes_2 = set([n for n in nodes if G.nodes[n]['bipartite']==2])
# set the location of the nodes for each set
pos = dict()
pos.update( (n, (1, i)) for i, n in enumerate(nodes_0) ) # put nodes from X at x=1
pos.update( (n, (2, i)) for i, n in enumerate(nodes_1) ) # put nodes from Y at x=2
pos.update( (n, (3, i)) for i, n in enumerate(nodes_2) ) # put nodes from X at x=1
color_map = []
for node in G:
if node in emc["entity"].values:
color_map.append("red")
elif node in EMM["id"].values:
color_map.append("green")
else:
color_map.append("blue")
nx.draw(G, pos, node_color=color_map, width= 2, with_labels=True, with_arrows=True)
This solution is only good to flip the position and is not useful for rotation. As I am not adding the nodes one by one therefore, this solution is not very helpful as well.
Instead of flipping the graph, have you looked into using pygraphviz or the hierarchical_pos solution as in the answer below?
Can one get hierarchical graphs from networkx with python 3?
the hierarchy_pos solution worked well to some extent for me:
## Converting the graph to an oriented tree through depth first search or breadth first search
tree_g = nx.dfs_tree(g, <starting_node>)
## attempt to draw it like a tree
pos = hierarchy_pos(tree_g)
nx.draw(tree_g, pos=pos,....)
https://networkx.github.io/documentation/networkx-1.10/reference/generated/networkx.algorithms.traversal.depth_first_search.dfs_tree.html
I tried to change the coordinates at the position(pos) variable and it worked. The part of the code that is different from what I posted above and has a solution is here
# set the location of the nodes for each set
pos = dict()
pos.update( (n, (i, -1)) for i, n in enumerate(nodes_0) )
pos.update( (n, (i, -2) for i, n in enumerate(nodes_1) )
pos.update( (n, (i, -3)) for i, n in enumerate(nodes_2) )

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)

Unique Paths II

A robot is located at the top-left corner of a m x n grid.
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid.
if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
class Solution:
"""
#param obstacleGrid: A list of lists of integers
#return: An integer
"""
def uniquePathsWithObstacles(self, obstacleGrid):
# write your code here
if not obstacleGrid:
return 0
m = len(obstacleGrid)
n = len(obstacleGrid[0])
li = [[0] * n] * m
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
li[i][j] = 0 ######## why do I have to add this line ???########
continue
elif i == 0 and j == 0:
li[i][j] = 1
elif i == 0:
li[i][j] = li[i][j - 1]
elif j == 0:
li[i][j] = li[i - 1][j]
else:
li[i][j] = li[i - 1][j] + li[i][j - 1]
return li[m - 1][n - 1]
The question is in the coding. I already set the matrix of m*n filling with zeros. Why should I assign the zero to the position one more time??? It seems that it won't work if I del that line. Can anyone tell me the reason why??? Thx!!!
The problem is this line:
li = [[0] * n] * m
The syntax [a] * n creates shallow copies, not deep copies of a.
Example:
n = m = 2
li[0][0] = 3
print(li) # prints [[3, 0], [3, 0]]
Link to question with discussion of possible solutions.

Splitting HSV mask into multiple rectangles

I created a HSV mask from the image. The result like following:
My goal is draw muliple rectangles that fit mask height or width, like following:
I encouter 2 problem.
I don't know how to locate the starting and ending point in mask for creating rectangle. If I use for loop to scan though mask row by row, it may split mask into 2 part.
Sometime, there also contain 2 different mask in one image. How can I draw rectangles?
Anyone can give me some suggestion?
You can search for your biggest contour (cross-like shape) with cv2.findContour(). It returns an array of coordinates of the contour. Then you can search your contour for point that has the highest X coordinate (that being your most right point), lowest X coordinat (most left point), highest Y coordinate (being most bottom point) and lowest Y coordinate (being your highest point). After you have all 4 values you can search the contour again for all points that have that values and append them in 4 different lists which you sort them later so you can get these points as drown on the bottom picture:
cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)
From this point forward I have transformed the lists into numpy arrays as it is easier for me. You can do it any other way.
Then it is just a matter of how many rectangles you want and how do you want to display them. In my example code you have to input how many same size rectangles you want and the last one is the size of what is left. I have first displayed rectangles on Y coordinate (green color) and then on X coordinate, which is divided on two segments (left and right) because they slightly vary in distance and I did not want to draw over the Y coordinate rectangles as they are not drawn on your example image. You can change the logic of writting the rectangles as you wish. Hope it helps a bit or give an idea on how to proceede. Cheers!
Example code:
import cv2
import numpy as np
# Read image and search for contours.
img = cv2.imread('cross.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
# Select the biggest contour (if you wish to segmentize only the cross-like contour).
cnt = max(contours, key=cv2.contourArea)
# Create empty lists for appending key points.
top_vertical = []
bottom_vertical = []
left_horizontal = []
right_horizontal = []
# Setting the starter values for N, S, E, W.
top = 10000
bottom = 0
left = 10000
right = 0
# Loop to get highest key values of N, S, E, W.
for i in cnt:
y = int(i[:,1])
x = int(i[:, 0])
if x < left:
left = int(x)
if x > right:
right = int(x)
if y < top:
top = int(y)
if y > bottom:
bottom = int(y)
# Loop for appending all points containing key values of N, S, E, W.
for i in cnt:
if int(i[:,1]) == top:
up = (int(i[:,0]), int(i[:,1]))
top_vertical.append(up)
if int(i[:,1]) == bottom:
down = (int(i[:,0]), int(i[:,1]))
bottom_vertical.append(down)
if int(i[:,0]) == left:
l = (int(i[:,0]), int(i[:,1]))
left_horizontal.append(l)
if int(i[:,0]) == right:
r = (int(i[:,0]), int(i[:,1]))
right_horizontal.append(r)
# Sorting the lists.
top_vertical.sort(key=lambda tup: tup[0])
bottom_vertical.sort(key=lambda tup: tup[0])
left_horizontal.sort(key=lambda tup: tup[1])
right_horizontal.sort(key=lambda tup: tup[1])
# Optional drawing of key points.
'''cv2.circle(img,(top_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(top_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[0]), 4, (0,0,255), -1)
cv2.circle(img,(bottom_vertical[-1]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(left_horizontal[-1]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[0]), 4, (0,0,255), -1)
cv2.circle(img,(right_horizontal[-1]), 4, (0,0,255), -1)'''
# Transforming lists to arrays.
top_vertical = np.array(top_vertical)
bottom_vertical = np.array(bottom_vertical)
left_horizontal = np.array(left_horizontal)
right_horizontal = np.array(right_horizontal)
# Calculating height and weight of the contour.
distance_y = bottom - top
distance_x = right - left
# Inputs for the number of same size segments.
a = input('Input the number of same size segments in Y coordinate: ')
b = input('Input the number of same size segments in left X coordinate: ')
c = input('Input the number of same size segments in right X coordinate: ')
# Calculation of area per segment and limit for the lenght of combined segments (height and weight) .
segment_y = distance_y/int(a)
segment_x_reference = int(top_vertical[0,0]) - int(left_horizontal[0,0])
segment_x = segment_x_reference/int(b)
segment_x_right_reference = int(right_horizontal[0,0]) - int(top_vertical[-1,0])
segment_x_right = segment_x_right_reference/int(c)
# Drawing rectangles on the Y axis.
for i in range(1,20):
sq = int(segment_y)*i
if sq < distance_y:
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),top_vertical[0,1] + sq),(0,255,0),1)
else:
sq = distance_y
cv2.rectangle(img,(top_vertical[0,0], top_vertical[0,1]),((top_vertical[-1,0]),sq),(0,255,0),1)
break
# Drawing rectangles on the left side of X axis.
for i in range(1,20):
sq = int(segment_x)*i
if sq < segment_x_reference:
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_reference
cv2.rectangle(img,(left_horizontal[0,0], left_horizontal[0,1]),((left_horizontal[0,0])+sq, left_horizontal[-1,1]),(255,0,0),1)
break
# Drawing rectangles on the right side of X axis.
for i in range(1,20):
sq = int(segment_x_right)*i
if sq < segment_x_right_reference:
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
else:
sq = segment_x_right_reference
cv2.rectangle(img,(right_horizontal[0,0], right_horizontal[0,1]),((right_horizontal[0,0])-sq, right_horizontal[-1,1]),(255,0,0),1)
break
# Displaying result.
cv2.imshow('img', img)
Result:
Input the number of same size segments in Y coordinate: 5
Input the number of same size segments in left X coordinate: 2
Input the number of same size segments in right X coordinate: 2

Resources