Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get shortest path to a cell in a 2D array in Python

I have a 2D array, arr, where each cell in it has a value 1, 2 or 3, for example, arr[0][0] = 3, arr[2][1] = 2, and arr[0][4] = 1.

I want to know the shortest path from a given certain cell, for example, arr[5][5] to the closest cell which has value 2 where the path shouldn't contain any cells that have the value 1. How can I do this?

Below is a script for the BFS, but how can I make it accept a 2D array as a graph and starting point as a certain cell location in the array and then go to the nearest two from this cell avoiding cells with 1s, so that it looks like bfs(2darray, starting location, 2)?

def bfs(graph, start, end):
    # Maintain a queue of paths
    queue = []

    # Push the first path into the queue
    queue.append([start])
    while queue:

        # Get the first path from the queue
        path = queue.pop(0)

        # Get the last node from the path
        node = path[-1]

        # Path found
        if node == end:
            return path

        # Enumerate all adjacent nodes, construct a new path and push it into the queue
        for adjacent in graph.get(node, []):
            new_path = list(path)
            new_path.append(adjacent)
            queue.append(new_path)

print bfs(graph, '1', '11')

Enter image description here

like image 568
Tak Avatar asked Dec 19 '17 23:12

Tak


People also ask

How do you find the shortest path in a 2D array?

Simplest approach to find the shortest path in a 2D array would be to use BFS technique in the following way. Problem: Given a 2D array with values as 'S', 'D', '1' and '0'. Find the shortest distance from S to D avoiding all the obstacles.

How do you slice a 2D list in Python?

As shown in the above syntax, to slice a Python list, you have to append square brackets in front of the list name. Inside square brackets you have to specify the index of the item where you want to start slicing your list and the index + 1 for the item where you want to end slicing.


2 Answers

You can use a simple breadth first search for this. Basically, each cell in your grid corresponds to a node in the graph, with edges between adjacent cells. Start at the starting position, and keep expanding passable cells until you find a goal cell.

def bfs(grid, start):
    queue = collections.deque([[start]])
    seen = set([start])
    while queue:
        path = queue.popleft()
        x, y = path[-1]
        if grid[y][x] == goal:
            return path
        for x2, y2 in ((x+1,y), (x-1,y), (x,y+1), (x,y-1)):
            if 0 <= x2 < width and 0 <= y2 < height and grid[y2][x2] != wall and (x2, y2) not in seen:
                queue.append(path + [(x2, y2)])
                seen.add((x2, y2))

Grid setup and results: (Note that I'm using symbols instead of numbers, simply for the reason that it's easier to visually parse the grid this way and to verify the solution.)

wall, clear, goal = "#", ".", "*"
width, height = 10, 5
grid = ["..........",
        "..*#...##.",
        "..##...#*.",
        ".....###..",
        "......*..."]
path = bfs(grid, (5, 2))
# [(5, 2), (4, 2), (4, 3), (4, 4), (5, 4), (6, 4)]
like image 159
tobias_k Avatar answered Oct 16 '22 01:10

tobias_k


If the list is not too big, the easiest solution I find is using the where function of the NumPy library to find the cells which have the value you are looking for. So, you will need to convert your list into a NumPy array.

The code below might be simplified to make it shorter and more efficient, but in this way it will be clearer. By the way, you can compute two kind of distances: the typical Euclidean and the Manhattan.

If there is more than one target cell at the same distance of the origin cell, min_coords corresponds to the first cell found (first by rows, then by columns).

import numpy as np

# The list needs to be transformed into an array in order to use the np.where method
# arr = np.random.randint(5, size=(6, 6))
arr = np.array([[0, 0, 0, 1, 1, 3],
                [0, 0, 2, 1, 1, 0],
                [0, 0, 1, 1, 1, 1],
                [3, 0, 3, 1, 1, 1], ])

# Origin cell to make the search
x0, y0 = (1, 1)
targetValue = 3

# This is the keypoint of the problem: find the positions of the cells containing the searched value
positions = np.where(arr == targetValue)
x, y = positions

dx = abs(x0 - x)  # Horizontal distance
dy = abs(y0 - y)  # Vertical distance

# There are different criteria to compute distances
euclidean_distance = np.sqrt(dx ** 2 + dy ** 2)
manhattan_distance = abs(dx + dy)
my_distance = euclidean_distance  # Criterion choice
min_dist = min(my_distance)
print(min_dist)

min_pos = np.argmin(my_distance)  # This method will only return the first occurrence (!)
min_coords = x[min_pos], y[min_pos]
print(min_coords)
like image 44
elthaniel Avatar answered Oct 16 '22 01:10

elthaniel