A-star (A*) search algorithm on labyrinth matrix in python [duplicate] - python

This question already has an answer here:
A star algorithm: Distance heuristics
(1 answer)
Closed 2 years ago.
I have a labyrinth matrix for a maze problem.
Labyrinth =
[[0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 1, 1, 3, 0],
[0, 0, 1, 1, 1, 0, 0, 0],
[0, 1, 2, 0, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0]]
Here,
0 represents a blocked cell that is a wall
1 represents an empty cell
2 and 3 represents starting and ending points respectively.
I need a function which can return the path from point 2 to 3 after performing an A* Search Algorithm using Manhattan distance as distance estimate and length of the current path as path-cost.
Any Pointers? or tip/clue how I should operate on this one?
Update: I want to return path from begin to end by marking the path with some other character like X. For reference, this:
Labyrinth =
[[0, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, X, X, 3, 0],
[0, 0, X, X, X, 0, 0, 0],
[0, 1, 2, 0, 1, 1, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 0]]

Classical search algorithm work using a set of states called the fringe and a set of visited states:
the fringe is all the set that are yet to eplore hoping to find the goal state
the visited set is all the states that have already been visited to avoid visiting them again
The idea of A* is to explore the state in the fringe that has a minimal value of cost (defined as the sum of the heuristic cost and the progression cost (computed by all the state you had to pass by before)). You can find generic implementation of this algorithm on the wikipedia page for A* search algorithm. In your case a state may consist in :
the i, j position in the grid
the previous state (assuming None for the first state)
the total cost of this state (heuristic + path cost).
To explore a set you only need to check the direct neighbors of the cell (including only the one where the value is one). It is worth noting that in the visited set you should only include the position (i,j) and the cost (as you may re-enter this state if you found a shorter path, even if it is unlikely in your problem).
Here is an example that works for your case (but may be generalized easily):
def astar(lab):
# first, let's look for the beginning position, there is better but it works
(i_s, j_s) = [[(i, j) for j, cell in enumerate(row) if cell == 2] for i, row in enumerate(lab) if 2 in row][0][0]
# and take the goal position (used in the heuristic)
(i_e, j_e) = [[(i, j) for j, cell in enumerate(row) if cell == 3] for i, row in enumerate(lab) if 3 in row][0][0]
width = len(lab[0])
height = len(lab)
heuristic = lambda i, j: abs(i_e - i) + abs(j_e - j)
comp = lambda state: state[2] + state[3] # get the total cost
# small variation for easier code, state is (coord_tuple, previous, path_cost, heuristic_cost)
fringe = [((i_s, j_s), list(), 0, heuristic(i_s, j_s))]
visited = {} # empty set
# maybe limit to prevent too long search
while True:
# get first state (least cost)
state = fringe.pop(0)
# goal check
(i, j) = state[0]
if lab[i][j] == 3:
path = [state[0]] + state[1]
path.reverse()
return path
# set the cost (path is enough since the heuristic won't change)
visited[(i, j)] = state[2]
# explore neighbor
neighbor = list()
if i > 0 and lab[i-1][j] > 0: #top
neighbor.append((i-1, j))
if i < height and lab[i+1][j] > 0:
neighbor.append((i+1, j))
if j > 0 and lab[i][j-1] > 0:
neighbor.append((i, j-1))
if j < width and lab[i][j+1] > 0:
neighbor.append((i, j+1))
for n in neighbor:
next_cost = state[2] + 1
if n in visited and visited[n] >= next_cost:
continue
fringe.append((n, [state[0]] + state[1], next_cost, heuristic(n[0], n[1])))
# resort the list (SHOULD use a priority queue here to avoid re-sorting all the time)
fringe.sort(key=comp)

Related

Python: distance from index to 1s in binary mask

I have a binary mask like this:
X = [[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 1],
[0, 0, 1, 1, 1, 1],
[0, 0, 0, 1, 1, 1]]
I have a certain index in this array and want to compute the distance from that index to the closest 1 in the mask. If there's already a 1 at that index, the distance should be zero.
Examples (assuming Manhattan distance):
distance(X, idx=(0, 5)) == 0 # already is a 1 -> distance is zero
distance(X, idx=(1, 2)) == 2 # second row, third column
distance(X, idx=(0, 0)) == 5 # upper left corner
Is there already existing functionality like this in Python/NumPy/SciPy? Both Euclidian and Manhattan distance would be fine.
I'd prefer to avoid computing distances for the entire matrix (as that is pretty big in my case), and only get the distance for my one index.
Here's one for manhattan distance metric for one entry -
def bwdist_manhattan_single_entry(X, idx):
nz = np.argwhere(X==1)
return np.abs((idx-nz).sum(1)).min()
Sample run -
In [143]: bwdist_manhattan_single_entry(X, idx=(0,5))
Out[143]: 0
In [144]: bwdist_manhattan_single_entry(X, idx=(1,2))
Out[144]: 2
In [145]: bwdist_manhattan_single_entry(X, idx=(0,0))
Out[145]: 5
Optimize further on performance by extracting the boudary elements only off the blobs of 1s -
from scipy.ndimage.morphology import binary_erosion
def bwdist_manhattan_single_entry_v2(X, idx):
k = np.ones((3,3),dtype=int)
nz = np.argwhere((X==1) & (~binary_erosion(X,k,border_value=1)))
return np.abs((idx-nz).sum(1)).min()
Number of elements in nz with this method would be smaller number than the earlier one, hence it improves.
You can use scipy.ndimage.morphology.distance_transform_cdt to compute the "taxicab" (Manhattan) distance transform:
import numpy as np
import scipy.ndimage.morphology
x = np.array([[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 1],
[0, 0, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 1],
[0, 0, 1, 1, 1, 1],
[0, 0, 0, 1, 1, 1]])
d = scipy.ndimage.morphology.distance_transform_cdt(1 - x, 'taxicab')
print(d[0, 5])
# 0
print(d[1, 2])
# 2
print(d[0, 0])
# 5
You can do it like this:
def Manhattan_distance(X, idx):
dist = min([ abs(i-idx[0]) + abs(j-idx[1]) for i, row in enumerate(X) for j, val in enumerate(X[i]) if val == 1])
return dist
Thanks.

Count Number Of Occurence in 2D Array

Let's say I have an array like this
grid:
[[1, 1, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 1]]
I want to isolate the group of "items" in this case 1's which are three groups the rule being the 0's are used to separate them like intersections. So this example has 3 groups of 1.
If you know how to do this with python, the first question I'd be asked is what I've tried as proof of not handing my homework to the community, the idea I had was to iterate down and left but that would have a high likelihood of missing some numbers since if you think about it, it would form a cross eminating from the top left and well this group is here to learn.
So for me and others who have an interest in this data science like problem be considerate.
If you do not need to know which sets are duplicates, you can use python's set built-in to determine unique items in a list. This can be a bit tricky since set doesn't work on a list of lists. However, you can convert this to a list of tuples, put those back in a list, and then get the len of that list to find out how many unique value sets there are.
grid = [[1, 1, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 1]]
unique = [list(x) for x in set(tuple(x) for x in grid)]
unique_count = len(unique) # this will return 3
Relatively straightforward depth first search based implementation of connected component labeling.
def get_components(grid, indicator=1):
def label(g, row, col, group):
if row >= 0 and col >= 0 and row < len(g) and col < len(g[row]) and g[row][col] == -1:
# only label if currently unlabeled
g[row][col] = group
# attempt to label neighbors with same label
label(g, row + 1, col, group)
label(g, row, col + 1, group)
label(g, row - 1, col, group)
label(g, row, col - 1, group)
return True
else:
return False
# initialize label grid as -1 for entries that need labeled
label_grid = [[-1 if gc == indicator else 0 for gc in gr] for gr in grid]
group_count = 0
for row, grid_row in enumerate(grid):
for col in range(len(grid_row)):
if label(label_grid, row, col, group_count + 1):
group_count += 1
return label_grid, group_count
The results of label_grid, group_count = get_components(grid) for your example inputs are
label_grid = [[1, 1, 0, 0, 0],
[1, 1, 0, 0, 0],
[0, 0, 2, 0, 0],
[0, 0, 0, 3, 3]]
group_count = 3
And for cases like the following
grid = [[1 0 1],
[1 1 1]]
we get group_count = 1.

In 2D binary matrix find the number of islands

I am trying to count the number of islands (a group of connected 1s forms an island) in a 2D binary matrix.
Example:
[
[1, 1, 0, 0, 0],
[0, 1, 0, 0, 1],
[1, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]
]
In the above matrix there are 5 islands, which are:
First: (0,0), (0,1), (1,1), (2,0)
Second: (1,4), (2,3), (2,4)
Third: (4,0)
Fourth: (4,2)
Fifth: (4,4)
To count the number of island in the 2D matrix, I am assuming the matrix as a Graph and then I am using DFS kind of algorithm to count the islands.
I am keeping track for the number of DFS (a recursive function) calls, because that many components would be there in the Graph.
Below is the code I wrote for this purpose:
# count the 1's in the island
def count_houses(mat, visited, i, j):
# base case
if i < 0 or i >= len(mat) or j < 0 or j >= len(mat[0]) or\
visited[i][j] is True or mat[i][j] == 0:
return 0
# marking visited at i, j
visited[i][j] = True
# cnt is initialized to 1 coz 1 is found
cnt = 1
# now go in all possible directions (i.e. form 8 branches)
# starting from the left upper corner of i,j and going down to right bottom
# corner of i,j
for r in xrange(i-1, i+2, 1):
for c in xrange(j-1, j+2, 1):
# print 'r:', r
# print 'c:', c
# don't call for i, j
if r != i and c != j:
cnt += count_houses(mat, visited, r, c)
return cnt
def island_count(mat):
houses = list()
clusters = 0
row = len(mat)
col = len(mat[0])
# initialize the visited matrix
visited = [[False for i in xrange(col)] for j in xrange(row)]
# run over matrix, search for 1 and then do dfs when found 1
for i in xrange(row):
for j in xrange(col):
# see if value at i, j is 1 in mat and val at i, j is False in
# visited
if mat[i][j] == 1 and visited[i][j] is False:
clusters += 1
h = count_houses(mat, visited, i, j)
houses.append(h)
print 'clusters:', clusters
return houses
if __name__ == '__main__':
mat = [
[1, 1, 0, 0, 0],
[0, 1, 0, 0, 1],
[1, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]
]
houses = island_count(mat)
print houses
# print 'maximum houses:', max(houses)
I get a wrong output for the matrix I have passed in argument. I get 7 but there are 5 clusters.
I tried debugging the code for any logical errors. But I couldn't find out where is the problem.
big hammer approach, for reference
had to add structure argument np.ones((3,3)) to add diagonal connectivity
import numpy as np
from scipy import ndimage
ary = np.array([
[1, 1, 0, 0, 0],
[0, 1, 0, 0, 1],
[1, 0, 0, 1, 1],
[0, 0, 0, 0, 0],
[1, 0, 1, 0, 1]
])
labeled_array, num_features = ndimage.label(ary, np.ones((3,3)))
labeled_array, num_features
Out[183]:
(array([[1, 1, 0, 0, 0],
[0, 1, 0, 0, 2],
[1, 0, 0, 2, 2],
[0, 0, 0, 0, 0],
[3, 0, 4, 0, 5]]), 5)
Your algorithm is almost correct except for the line 21:
if r != i and c != j:
cnt += count_houses(mat, visited, r, c)
Instead you want to use or as you want to continue counting provided at least one of the coordinate is not the same as your center.
if r != i or c != j:
cnt += count_houses(mat, visited, r, c)
An alternate and more intuitive way to write this would be the following
if (r, c) != (i, j):
cnt += count_houses(mat, visited, r, c)

How can I improve the speed of this shortest path/shortcut(array graph DS) solution?

Given a maze as an array of arrays where 1 is a wall and 0 is a passable area:
Must include start node in distance, if you BFS this it will give you 21.
[0][0] is the start point.
|
[ V
[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0]<-- [-1][-1] is the end point.
]
We must find the shortest path possible, we can remove one '1' to help create a shortcut.
The shortcut that creates the shortest path is changing [1][0] to 0, opening a path that makes the distance 11.
[
[0, 0, 0, 0, 0, 0],
-->[0, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1],
[0, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0]
]
return 11
My original thought process was run through every element and check if it's == 1, then do a bfs compare the distance with the min.
But ofcourse that was too slow. So I thought running through every element and check if it's 1, then see if it has exactly two neighbors that are passable because that seems to the only possible case where a shortcut is meaningful.
Here is my code:
import copy
def bfs(maze):
visited = set()
queue = []
mazeHeight = len(maze)
mazeWidth = len(maze[0])
queue.append(((0,0),1))
while queue:
yx,distance = queue.pop(0)
y,x = yx
visited.add(yx)
if yx == (mazeHeight-1,mazeWidth-1):
return distance
if y+1 < mazeHeight:
if not maze[y+1][x] and (y+1,x) not in visited:
queue.append(((y+1,x),distance+1))
if y-1 >= 0:
if not maze[y-1][x] and (y-1,x) not in visited:
queue.append(((y-1,x),distance+1))
if x+1 < mazeWidth:
if not maze[y][x+1] and (y,x+1) not in visited:
queue.append(((y,x+1),distance+1))
if x-1 >= 0:
if not maze[y][x-1] and (y,x-1) not in visited:
queue.append(((y,x-1),distance+1))
return False
def answer(maze):
min = bfs(maze)
mazeHeight = len(maze)
mazeWidth = len(maze[0])
for y in range(mazeHeight):
for x in range(mazeWidth):
if maze[y][x]:
oneNeighbors = 0
if y+1 < mazeHeight:
if not maze[y+1][x]:
oneNeighbors += 1
if y-1 >= 0:
if not maze[y-1][x]:
oneNeighbors += 1
if x+1 < mazeWidth:
if not maze[y][x+1]:
oneNeighbors += 1
if x-1 >= 0:
if not maze[y][x-1]:
oneNeighbors += 1
if oneNeighbors == 2:
tmpMaze = copy.deepcopy(maze)
tmpMaze[y][x] = 0
tmpMin = bfs(tmpMaze)
if tmpMin < min:
min = tmpMin
return min
print(answer([[0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]]))
Any suggestions to improve the speed?
You seem to be on the right track. The following approach can be considered:
Form a graph of n x m nodes where n and m are the dimensions of the maze matrix.
There is an edge of cost zero between two nodes if they are adjacent 0s. There is an edge of cost one between two nodes if they are both 0s separated by a 1.
(Note that there shall be two costs that you shall need to maintain for each path, one is the above zero-one cost and the other is the number of nodes in the path to keep track of the minimum).
Then perform BFS and consider only paths that have a zero-one cost <= 1.
This shall give you a linear time algorithm (linear in number of nodes).
Following code may contain bugs but it should get you started.
def bfs(maze):
visited = set()
queue = []
mazeHeight = len(maze)
mazeWidth = len(maze[0])
queue.append(((0,0),1,0))
while queue:
yx,distance, cost = queue.pop(0)
y,x = yx
visited.add(yx)
if yx == (mazeHeight-1,mazeWidth-1):
return distance
if y+1 < mazeHeight:
if not maze[y+1][x] and (y+1,x) not in visited:
queue.append(((y+1,x),distance+1, cost))
if y-1 >= 0:
if not maze[y-1][x] and (y-1,x) not in visited:
queue.append(((y-1,x),distance+1, cost))
if x+1 < mazeWidth:
if not maze[y][x+1] and (y,x+1) not in visited:
queue.append(((y,x+1),distance+1, cost))
if x-1 >= 0:
if not maze[y][x-1] and (y,x-1) not in visited:
queue.append(((y,x-1),distance+1, cost))
if cost == 0:
if y+2 < mazeHeight:
if not maze[y+2][x] and (y+2,x) not in visited and maze[y+1][x] == 1:
queue.append(((y+2,x),distance+2, cost+1))
if y-1 >= 0:
if not maze[y-2][x] and (y-2,x) not in visited and maze[y-1][x] == 1:
queue.append(((y-2,x),distance+2, cost+1))
if x+1 < mazeWidth:
if not maze[y][x+2] and (y,x+2) not in visited and maze[y][x+1] == 1:
queue.append(((y,x+2),distance+2, cost+1))
if x-1 >= 0:
if not maze[y][x-2] and (y,x-2) not in visited and maze[y][x-1] == 1:
queue.append(((y,x-2),distance+2, cost+1))
return False

reordering for FFT in python

I'm new to python and FFT. I have taken a small task in Python to find the shuffling order for a given number of datapoints.
My objective is to have an output like below for N datapoints. Here N=8, so we have 3 sets:
[0, 1, 0, 1, 0, 1, 0, 1]
[0, 0, 1, 1, 0, 0, 1, 1]
[0, 0, 0, 0, 1, 1, 1, 1]
The code I tried is below. Could someone help me where I'm wrong and suggest modifications to the code to produce the desired output.
le=8
steps=int(math.ceil(math.log(le,2)))
pos2=[]
m=0
for k in range(0,steps):
x=2**k
#print x
pos1=[]
for i in range(0,le):
if m<x:
pos1.append(0)
m=m+1
else:
pos1.append(1)
m=0
pos2.append(pos1)
You immediately get back to appending 0s after appending only one 1. Here is a working version with slightly different logic:
import math
le = 8
steps = int(math.ceil(math.log(le, 2)))
pos2 = []
for k in range(0, steps):
x = 2**k
pos1 = []
while len(pos1) < le:
for i in range(0, x):
pos1.append(0)
for i in range(0, x):
pos1.append(1)
pos2.append(pos1)
print pos1
this will print
[0, 1, 0, 1, 0, 1, 0, 1]
[0, 0, 1, 1, 0, 0, 1, 1]
[0, 0, 0, 0, 1, 1, 1, 1]
and here is a one-liner for you to examine:
import math
le = 8
pos2 = [[(i // 2**k) % 2 for i in range(le)] for k in range(int(math.ceil(math.log(le, 2))))]
print pos2

Categories

Resources