Python shortest distance while escaping a binary maze using backtracking - python

Need help with finding shortest distance in a binary maze, which is a list of lists matrix, where 0 is an empty cell and 1 is a wall. The maze has x,y starting coordinates that default to 0,0 and it's end point is always bottom right corner. Shortest distance always includes the starting point (i.e., if there are four steps needed from the starting point, shortest distance will be 5)
I need to be using backtracking algorithm. So far I could come up with a function that determines if there is an escaping path at all. It works well:
def is_solution(maze, x=0, y=0):
n = len(maze)
m = len(maze[0])
if x == n - 1 and y == m - 1:
return True
maze[x][y] = 1
result = False
for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)]:
if 0 <= a < n and 0 <= b < m and maze[a][b] == 0:
result = result or is_solution(maze, a, b)
maze[x][y] = 0
return result
maze = [
[0, 0, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0]
]
is_solution(maze)
The above will result to True.
Now I am really struggling with finding the shortest distance. I think it should be relatively easy to update the code above so it showed distance if there is a path and inf if there isn't one. But I got stuck. In the example above shortest distance would be 6 (including the starting point)
I also need to add code to be able to get a list of all shortest distances and coordinates of each step in a list of lists format like [[(0, 0), (0, 1), (1, 1), (1, 2), (1, 3), (2, 3)]] . In this case there is only one path, but if there were two of distance six, that list would include also the list of second shortest path as well.
Thank you.

Simple change to your code to track shortest path
Shortest Path
def min_solution(maze, x = 0, y = 0, path = None):
def try_next(x, y):
' Next position we can try '
return [(a, b) for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] if 0 <= a < n and 0 <= b < m]
n = len(maze)
m = len(maze[0])
if path is None:
path = [(x, y)] # Init path to current position
# Reached destionation
if x == n - 1 and y == m - 1:
return path
maze[x][y] = 1 # Mark current position so we won't use this cell in recursion
# Recursively find shortest path
shortest_path = None
for a, b in try_next(x, y):
if not maze[a][b]:
last_path = min_solution(maze, a, b, path + [(a, b)]) # Solution going to a, b next
if not shortest_path:
shortest_path = last_path # Use since haven't found a path yet
elif last_path and len(last_path) < len(shortest_path):
shortest_path = last_path # Use since path is shorter
maze[x][y] = 0 # Unmark so we can use this cell
return shortest_path
maze = [
[0, 0, 1, 1],
[1, 0, 0, 0],
[1, 1, 1, 0]
]
t = min_solution(maze)
if t:
print(f'Shortest path {t} has length {len(t)}')
else:
print('Path not found')
Output:
Shortest path [(0, 0), (0, 1), (1, 1), (1, 2), (1, 3), (2, 3)] has length 6
All Paths
def all_paths(maze, x = 0, y = 0, path = None):
'''
All paths through Maze as a generator
'''
def try_next(x, y):
' Next position we can try '
return [(a, b) for a, b in [(x - 1, y), (x, y - 1), (x + 1, y), (x, y + 1)] if 0 <= a < n and 0 <= b < m]
n = len(maze)
m = len(maze[0])
if path is None:
path = [(x, y)]
# Reached destionation
if x == n - 1 and y == m - 1:
yield path
else:
maze[x][y] = 1 # Mark current position so we won't use this cell in recursion
# Recursively find pat
for a, b in try_next(x, y):
if not maze[a][b]:
yield from all_paths(maze, a, b, path + [(a, b)]) # Solution going to a, b next
maze[x][y] = 0 # Unmark so we can use this cell
maze = [[0, 0, 0],
[1, 0, 0],
[1, 1, 0]]
for t in all_paths(maze):
print(f'path {t} has length {len(t)}')
Output
path [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2)] has length 5
path [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)] has length 5

Related

Generating a specific adjacency matrix in Python

I have a network with nodes and vertices and the following numbering scheme. I want to generate an adjacency matrix A for the nodes 0,1 as shown below. I tried to do using networkx. I present the current and expected outputs.
import networkx as nx
N=2
def pos():
x, y = 1, N + 3 - 1
for _ in range(N):
yield (x, y)
y -= (x + 2) // (N+1 )
x = (x + 2) % (N+1)
G = nx.Graph()
it_pos = pos()
for u in range(N):
G.add_node(u+1, pos=next(it_pos))
if u % (2 * N) < N:
for v in (u - 2 * N, u - N, u - N):
if G.has_node(v + 1):
G.add_edge(u + 2, v + 2)
elif u % (2 * N) == N:
G.add_edge(u + 1, u - N + 1)
elif u % (2 * N + 1) < 2 * N:
for v in (u - 1, u - N, u - N):
G.add_edge(u + 1, v + 1)
else:
for v in (u - 1, u - N - 1):
G.add_edge(u + 1, v + 1)
nx.draw(G, nx.get_node_attributes(G, 'pos'), with_labels=True, font_weight='bold')
Nodes=len(G.nodes)
A=nx.adjacency_matrix(G).todense()
The current output is
A=matrix([[0., 0.],
[0., 0.]])
The expected output is
You want the adjacency matrix between node and its edges, but the function you are using looks for neighbouring nodes.
In order to build your network and get your matrix, you could do the following:
import networkx as nx
import numpy as np
import pandas as pd
# build the network with relevant edges
G = nx.Graph()
points = {
0: (1, 1), 1: (2, 1),
'a':(1, 2), 'b':(2, 2),
'c':(0, 1), 'd':(3, 1),
'e':(1, 0), 'f':(2, 0)
}
for key, pos in points.items():
G.add_node(key, pos=pos)
G.add_edge('a', 0, name=0)
G.add_edge('b', 1, name=1)
G.add_edge('c', 0, name=2)
G.add_edge(0, 1, name=3)
G.add_edge(1, 'd', name=4)
G.add_edge(0, 'e', name=5)
G.add_edge(1, 'f', name=6)
# find connected edges to nodes 0 and 1
my_nodes = [0, 1] # could be more here
edges = {
node: [G.get_edge_data(*edge)['name'] for edge in G.edges(node)]
for node in my_nodes
}
# build matirx
mat = np.zeros((len(my_nodes), 7), dtype=np.uint8)
for i, node in enumerate(my_nodes)):
mat[i, edges[node]] = 1
mat[i, edges[node]] = 1
A = pd.DataFrame(mat)
A
Edit: generalize the connection search.
You can implement the matrix in Python using a nested list:
A = [[1, 0, 1, 1, 0, 1, 0], [0, 1, 0, 1, 1, 0, 1]]

nested list with multiple conditions

Trying to create a list of tuples like so:
m=[-1,0,1]
[(self._x+x,self._y) for x in m for y in m]
But I want to exclude the tuple when both x and y are equal to 0. I've tried:
[(self._x+x,self._y) for x in m for y in m if x!=0 and y!=0]
but it doesn't work.
Thanks.
m = [-1, 0, 1]
# so you want all combinations x and y out of m
# but only if x and y are not both 0 at the same time?
[ (x, y) for x in m for y in m if not (x == 0 and y == 0) ]
## => [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
Your code's problem are the self. - which occurs only within class definitions.
self._x + x is x from the for loop plus a certain self._x value, whatever that is. self._y is not y.
What you want with this actually? you don't use y from the second for loop at all in your code.

Determining neighbours of cell as diamond shape in python

I have a matrix variable in size where 1 indicates the cell such as:
Cells = [[0,0,0,0,0],
[0,0,0,0,0],
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
]
I need to find neigbours in a parametric sized diamond shape. Not a box as answer given in here or not a fixed sized 1 diamond, answer given here. For example, N=2 I want to know the column, rows for below:
Mask = [[0,0,1,0,0],
[0,1,1,1,0],
[1,1,0,1,1],
[0,1,1,1,0],
[0,0,1,0,0],
]
The function should receive x and y for the requested column and row, (for above I will input 2,2) and N (input 2) the size of diamond. The function should return list of tuples (x,y) for the given diamond size.
I struggled at defining the shape as a function of x, y and k in for loops. I need to know both numpy (if there is anything that helps) and non-numpy solution.
For an iterative approach where you just construct the diamond:
def get_neighbors(center, n=1):
ret = []
for dx in range(-n, n + 1):
ydiff = n - abs(dx)
for dy in range(-ydiff, ydiff + 1):
ret.append((center[0] + dx, center[1] + dy))
return ret
Result of get_neighbors((2, 2), 2):
[(0, 2), (1, 1), (1, 2), (1, 3), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 1), (3, 2), (3, 3), (4, 2)]
Or, for a recursive approach:
dirs = [(1, 0), (0, 1), (-1, 0), (0, -1)]
def add_tuples(a, b):
return tuple([x + y for (x, y) in zip(a, b)])
def get_neighbors(center, n=1, seen=set()):
seen.add(center)
if n <= 0:
return seen
for dir in dirs:
newpos = add_tuples(center, dir)
if newpos in seen:
continue
get_neighbors(newpos, n - 1, seen)
return seen
I would start by taking out a "sub-matrix" that is the smallest square that can contain your result cells. This is the part that numpy should be able to help with.
Then define a function that calculates the manhattan distance between two cells (abs(x - x_p) + abs(y - y_p)) and iterate through the cells of your sub-matrix and return the values with a manhattan distance of less than N from your origin.
Make mask with rotation
Convolute cell and mask
Fix the result
import numpy as np
from scipy.ndimage import rotate, convolve
import matplotlib.pyplot as plt
def diamond_filter(radius):
s = radius * 2 + 1
x = np.ones((s, s), dtype=int)
x[radius, radius] = 0
return rotate(x, angle=45)
def make_diamonds(x, radius):
filter = diamond_filter(radius)
out = convolve(x, filter)
out[out > 1] = 1
out -= x
out[out < 0] = 0
return out
def plot(x):
plt.imshow(x)
plt.show()
plt.close()
def main():
cell = np.random.choice([0, 1], size=(200, 200), p=[0.95, 0.05])
plot(diamond_filter(2))
plot(cell)
result = make_diamonds(cell, 2)
plot(result)
if __name__ == '__main__':
main()

Path finding in 2D map with python

I have been given a maze in the form of a labelmap (the matrix pixel either have value 1 or 0). I cannot cross the pixels with value 0. Given a starting point (x1,y1) and an end point (x2,y2), I have to find all possible paths between the two points using python. Is there a way to do this? Is there an algorithm that allows me to find ALL possible paths and save them?
Thank you!
Following Python code based upon modifying C++ routine for counting paths.
Code
# Check if cell (x, y) is valid or not
def is_valid_cell(x, y, N):
if x < 0 or y < 0 or x >= N or y >= N:
return False
return True
def find_paths_util(maze, source, destination, visited, path, paths):
"""Find paths using Breadth First Search algorith """
# Done if destination is found
if source == destination:
paths.append(path[:]) # append copy of current path
return
# mark current cell as visited
N = len(maze)
x, y = source
visited[x][y] = True
# if current cell is a valid and open cell,
if is_valid_cell(x, y, N) and maze[x][y]:
# Using Breadth First Search on path extension in all direction
# go right (x, y) --> (x + 1, y)
if x + 1 < N and (not visited[x + 1][y]):
path.append((x + 1, y))
find_paths_util(maze,(x + 1, y), destination, visited, path, paths)
path.pop()
# go left (x, y) --> (x - 1, y)
if x - 1 >= 0 and (not visited[x - 1][y]):
path.append((x - 1, y))
find_paths_util(maze, (x - 1, y), destination, visited, path, paths)
path.pop()
# go up (x, y) --> (x, y + 1)
if y + 1 < N and (not visited[x][y + 1]):
path.append((x, y + 1))
find_paths_util(maze, (x, y + 1), destination, visited, path, paths)
path.pop()
# go down (x, y) --> (x, y - 1)
if y - 1 >= 0 and (not visited[x][y - 1]):
path.append((x, y - 1))
find_paths_util(maze, (x, y - 1), destination, visited, path, paths)
path.pop()
# Unmark current cell as visited
visited[x][y] = False
return paths
def find_paths(maze, source, destination):
""" Sets up and searches for paths"""
N = len(maze) # size of Maze is N x N
# 2D matrix to keep track of cells involved in current path
visited = [[False]*N for _ in range(N)]
path = [source]
paths = []
paths = find_paths_util(maze, source, destination, visited, path, paths)
return paths
Test
# Test Maze
maze = [
[1, 1, 1, 1],
[1, 1, 0, 1],
[0, 1, 0, 1],
[1, 1, 1, 1]
]
N = len(maze)
# Start point and destination
source = (0, 0) # top left corner
destination = (N-1, N-1) # bottom right corner
# Find all paths
paths = find_paths(maze, source, destination)
print("Paths with '->' separator between maze cell locations")
for path in paths:
print(*path, sep = ' -> ')
Output
Paths with '->' separator between maze cell locations
(0, 0) -> (1, 0) -> (1, 1) -> (2, 1) -> (3, 1) -> (3, 2) -> (3, 3)
(0, 0) -> (1, 0) -> (1, 1) -> (0, 1) -> (0, 2) -> (0, 3) -> (1, 3) -> (2, 3) -> (3, 3)
(0, 0) -> (0, 1) -> (1, 1) -> (2, 1) -> (3, 1) -> (3, 2) -> (3, 3)
(0, 0) -> (0, 1) -> (0, 2) -> (0, 3) -> (1, 3) -> (2, 3) -> (3, 3)

How to change nearest neighbors function to not work diagonally

I've working nearest neighbors function but I don't know how to make it work only horizontally and vertically right now it works in all directions. Code below:
nnlst = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
MAP_WIDTH = 3
MAP_HEIGHT = 3
def nearest_neighbors(map_x, map_y):
coordinates_list = []
for x_ in range(max(0, map_x - 1), min(MAP_WIDTH, map_x + 2)):
for y_ in range(max(0, map_y - 1), min(MAP_HEIGHT, map_y + 2)):
# we are ignoring result when x_ and y_ equals variable we ask for
if (map_x, map_y) == (x_, y_):
continue
coordinates_list.append([x_, y_])
return coordinates_list
print "function result"
print "nearest neighbors of", nnlst[0][1]
nearest_neighbor_coordinates_list = nearest_neighbors(0, 1)
for coordinates in nearest_neighbor_coordinates_list:
print coordinates, "=", nnlst[coordinates[0]][coordinates[1]]
As you can see right now it works in all directions.
You need to add one more condition to prevent inclusion of the diagonal ones:
def nearest_neighbors(map_x, map_y):
coordinates_list = []
for x_ in range(max(0, map_x - 1), min(MAP_WIDTH, map_x + 2)):
for y_ in range(max(0, map_y - 1), min(MAP_HEIGHT, map_y + 2)):
# we are ignoring result when x_ and y_ equals variable we ask for, also the diagonal neigbors that differ in both x & y coordinates
if (map_x, map_y) == (x_, y_) or (map_x != x_ and map_y != y_):
continue
coordinates_list.append([x_, y_])
return coordinates_list
to get the desired result:
function result
nearest neighbors of 2
[0, 0] = 1
[0, 2] = 3
[1, 1] = 5
alternatively, you could list all "admissible" displacements explicitly:
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
x_ = min(MAP_WIDTH, max(0, map_x + dx))
y_ = min(MAP_HEIGHT, max(0, map_y + dy))
if (map_x, map_y) == (x_, y_):
continue
...
For a problem with such as small number of possibilities, I would just spell them all out and pre-compute the function results for every position. That way the function can be eliminated and the problem reduced to doing simple table look-up operation.
Here's what I mean:
nnlist = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
MAP_WIDTH = len(nnlist[0])
MAP_HEIGHT = len(nnlist)
nearest_neighbors = {} # is now a dictionary
for x in range(MAP_WIDTH):
for y in range(MAP_HEIGHT):
neighbors = [[nx, ny] for nx, ny in [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
if -1 < nx < MAP_WIDTH and -1 < ny < MAP_HEIGHT]
nearest_neighbors[(x, y)] = neighbors
print "look-up result"
print "nearest neighbors of", nnlist[0][1]
nearest_neighbor_coordinates_list = nearest_neighbors[(0, 1)]
for coordinates in nearest_neighbor_coordinates_list:
print coordinates, "=", nnlist[coordinates[0]][coordinates[1]]

Categories

Resources