JackPieCZ
JackPieCZ

Reputation: 133

How to find shortest path in X*Y grid

I have a grid N*M in Python.

where

"X" represents the border,

"1" represents current position,

"2" represents finish, and

"3" represents forbidden locations.

The last thing is that (imagine you're a car) you can go only straight or right.

Example 3x3 except borders:

[X,X,X,X,X]
[X,2,1,0,X]
[X,0,3,0,X]
[X,0,0,0,X]
[X,X,X,X,X]

Another example:

[X,X,X,X,X]
[X,2,0,0,X]
[X,3,3,1,X]
[X,X,X,X,X]

Or another:

[X,X,X,X,X,X,X]
[X,0,2,0,0,0,X]
[X,0,3,0,3,0,X]
[X,0,0,0,0,0,X]
[X,0,3,0,3,0,X]
[X,0,3,1,3,0,X]
[X,X,X,X,X,X,X]

Do you have any suggestion for concrete script that will find the fastest way?

And if there's not, print("No solution")?

Thank you very much!

To help you understand these situations:

pictures of examples 1 and 2

Upvotes: 1

Views: 1681

Answers (2)

jpf
jpf

Reputation: 1475

This was a fun one. A brute force approach calculates all possible paths through the system, and finally finds minimum path length of those ending in 2. Note that init just returns a valid matrix with which to work. If no solution is found for the matrix, None is returned by solve function.

BLOCK=3
FINAL=2
TERMINATE=None
INITIAL=1
PATH=0

def init(n,m):
  # returns array with essential components, but not necessarily with solution
  import numpy
  import random
  while True:
    try:
      a=numpy.zeros((n,m),dtype=int)
      for i in range(n):
        for j in range(m):
          t=INITIAL
          # initialize to anything but INITIAL
          while t==INITIAL: t=numpy.random.randint(PATH,BLOCK)
          a[i,j]=t
      c=random.choice(list(zip(*(a==PATH).nonzero())))
      a[c]=INITIAL
      finals=list(zip(*(a==FINAL).nonzero()))
      c=random.choice(finals)
      for i in finals:
        if i!=c: a[i]=BLOCK
      break
    except:
      continue
  return a

def possible_moves(a,pos):
  n,m=a.shape
  test_moves=[(0,1),(1,0),(-1,0),(0,-1)]
  #print('from pos={}'.format(pos))
  x,y=pos[0:2]
  valid_moves=[]
  for t in test_moves:
    new=(t[0]+x,t[1]+y)
    if t[0]+x>-1 and \
       t[0]+x<n and \
       t[1]+y>-1 and \
       t[1]+y<m:
      if a[new] not in [BLOCK]:
        valid_moves.append((t[0]+x,t[1]+y))
  return valid_moves

def allpaths(a,paths):
  for path in paths:
    if path[-1] in [TERMINATE,FINAL]: continue
    pos=path[-1]
    moves=possible_moves(a,pos)
    if not moves:
      path.append(TERMINATE) # if no moves left, path is terminated
      continue
    base=path.copy()
    for im,move in enumerate(moves):
      b=a.copy()
      # set previous position to BLOCK so can't move into previous positions
      b[pos]=BLOCK
      if im==0:
        if a[move]==FINAL: path.append(FINAL) # if position to move to is FINAL, indicate
        else: path.append(move)
      else:
        if a[move]==FINAL: paths.append(base+[FINAL])
        else: paths.append(base+[move])
    paths=allpaths(b,paths)
  return paths

def solve(a):
  ipos=list(zip(*(a==INITIAL).nonzero()))
  paths=[ipos]
  paths=allpaths(a,paths)
  M=a.shape[0]*a.shape[1]
  minlen=M
  for path in paths:
    if path[-1] in [FINAL]:
      minlen=min(minlen,len(path)-1)
  if minlen==M: return None
  else: return minlen

n,m=5,6
a=init(n,m)
print(n,m)
print(a)

minlen=solve(a)
print(minlen)

To capture "right," you could simply track previous position through to the possible_moves function, and ensure you are returning only "straight" and "right" directions ("backward" is already guarded against as it's encoded).

Upvotes: 1

n1tr0xs
n1tr0xs

Reputation: 407

You can use this code, which based on Grap theory:

grid = [
['X','X','X','X','X','X','X'],
['X',2,0,0,0,0,'X'],
['X',0,3,0,3,0,'X'],
['X',0,0,0,0,0,'X'],
['X',0,3,0,3,0,'X'],
['X',0,3,1,3,0,'X'],
['X','X','X','X','X','X','X']
]


INF=1000000

def is_valid(grid:list):
    ends = 0
    starts = 0
    for line in grid:
        for el in line:
            if el==2:
                ends+=1
            if el==1:
                starts+=1
    return False if ends!=1 or starts!=1 else True

def redef_grid(grid:list):
    ''' Deleting from grid all 'X' '''
    g=grid.copy()
    if 'X' in g[0]: del g[0]
    if 'X' in g[-1]: del g[-1]
    for line in g:
        while 'X' in line:
            line.remove('X')
    return g

def uni_index(grid:list, pos:tuple):
    return len(grid[0])*pos[0]+pos[1]

def real_index(grid:list, index:int):
    row =index//len(grid[0])
    col=index-row*len(grid[0])
    return (row, col)

def get_neibs(grid:list, index:int):
    def get_vertical(grid:list, index:int):
        return [index+k for k in [len(grid[0]), -len(grid[0])] if index+k>=0 and index+k<uni_index(grid, (len(grid)-1, len(grid[0])-1))]
    def get_horizontal(grid:list, index:int):
        return [index+k for k in [1, -1] if index+k>=(index//len(grid[0]))*(len(grid[0])) and index+k<=uni_index(grid, (index//len(grid[0]), len(grid[0])-1))]
    return get_vertical(grid, index)+get_horizontal(grid, index)

def get_matrix(grid:list):
    last_el = uni_index(grid, (len(grid)-1, len(grid[0])-1))
    elements=len(grid[0])*len(grid)
    matrix=[[INF for _ in range(elements)] for _ in range(elements)]
    for index in range(last_el):
        neibs=get_neibs(grid, index)
        neibs=[neib for neib in neibs if grid[real_index(grid, neib)[0]][real_index(grid, neib)[1]]!=3]
        for neib in neibs:
            matrix[index][neib]=1
    return matrix

def get_start(grid:list):
    for i in range(len(grid)):
        try:
            return (i, grid[i].index(1))
        except ValueError:
            pass

def get_end(grid:list):
    for i in range(len(grid)):
        try:
            return (i, grid[i].index(2))
        except ValueError:
            pass

def Dijkstra(N, S, matrix):
    valid = [True]*N
    weight = [1000000]*N
    weight[S] = 0
    for i in range(N):
        min_weight = 1000001
        ID_min_weight = -1
        for i in range(N):
            if valid[i] and weight[i] < min_weight:
                min_weight = weight[i]
                ID_min_weight = i
        for i in range(N):
            if weight[ID_min_weight] + matrix[ID_min_weight][i] < weight[i]:
                weight[i] = weight[ID_min_weight] + matrix[ID_min_weight][i]
        valid[ID_min_weight] = False
    return weight

grid=redef_grid(grid)
if is_valid(grid):
    matrix=get_matrix(grid)
    paths = Dijkstra(len(grid)*len(grid[0]), uni_index(grid, get_start(grid)), matrix)
    shortest_path = paths[uni_index(grid, get_end(grid))]
    if shortest_path==INF: shortest_path='No solution'
    print(shortest_path)
else: print('Invalid grid')

Upvotes: 1

Related Questions