# Python: AStar Pathfinder algorithm

Traverse through any binary array using the Manhattan distance by giving a start node location and an end node location. The neighbor() method gives the neighbors next to the x,y coordinates that are == to 0, the f_value() method assigns a value to the neighboring nodes based on a heuristic, and the path() method tells the algorithm which neighbor it should move to, based on the f_values.

Any ways I can make this algorithm more efficient, specifically with loops/modules?

Also, any tips on the pythonic nature of my code would be appreciated as well.

    '''
@author: BenNicholl
'''
#  please cite algorithm if used
#  Algorithm changes the integer values of the grid array being used

import numpy as np

nmap = np.array([
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,0,1,1,1,1,1,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,0,1,1,1,1,1,1,1,1,1,1,1,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,0,1,1,1,1,1,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,0,1,1,1,1,1,1,1,0,1,1,1,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,1,1,1,1,1,1,1,1,0,1],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0]])

class AStar():

def __init__(self, grid, x, y, goal_x, goal_y)    :
self.grid = grid
self.initial_x = x  #  never changes
self.initial_y = y  #  never changes
self.x = self.initial_x  #  does change
self.y = self.initial_y  #  does change
self.goal_x = goal_x  # never changes
self.goal_y = goal_y  #  never changes
self.neighbors = []
self.neighbor()

def neighbor(self):
#  if x and y == goal_x and goal_y, algo is complete
if self.x == self.goal_x and self.y == self.goal_y:

#  sets the x and y coordinates to 1 so they are not revisited
self.grid[self.x][self.y] = 1

#  the below if statements give you the coordinates of
#  the neighbors that have a value of 0
if self.x > 0:  #  if X will be on the grid if it moves UP 1 node
if self.grid[self.x-1] [self.y] == 0:
self.neighbors.append([self.x - 1, self.y])

if self.x < self.grid.shape[0] - 1:  #  if X will be on the grid if it moves DOWN 1 node
if self.grid[self.x+1][self.y] == 0:
self.neighbors.append([self.x+1,self.y])

if self.y > 0: #  If Y will be on grid if it moves LEFT 1 node
if self.grid[self.x][self.y - 1] == 0:
self.neighbors.append([self.x, self.y - 1]) #

if self.y < self.grid.shape[1] - 1: #  If Y will be on grid if it moves RIGHT 1 node
if self.grid[self.x][self.y + 1] == 0:
self.neighbors.append([self.x, self.y + 1])

self.f_value()

#  this method gives you the f_value off all the neighbors
def f_value(self):
h_values = []
g_values = []

#  calculate distance from the neighboring X, Y to end X, Y value using Manhattan distance
for i in self.neighbors:
x_distance = abs(i[0] - self.goal_x)
y_distance = abs(i[1] - self.goal_y)
h_value = (x_distance + y_distance)
h_values.append(h_value)

#  calculate distance from the neighboring X, Y to the initial X, Y values
for i in self.neighbors:
x_distance = abs(i[0] - self.initial_x)
y_distance = abs(i[1] - self.initial_y)
g_value = (x_distance + y_distance)
g_values.append(g_value)

#  add g_value and h_value to calculate f_value
self.f_values = [h + g for h, g in zip(h_values, g_values)]
self.path()

#  gets the lowest f_value, then pops that f_value and corresponding neighbor
#  the neighbor always has the same index as the f_values
def path(self):

for coordinate, i in enumerate(self.f_values):
if i <= min(self.f_values):
value = i
index = coordinate

self.x = self.neighbors[index][0]
self.y = self.neighbors[index][1]

print('f_values:', self.f_values)
print('neighbors:', self.neighbors)
print('x:', self.x)
print('y:', self.y)

self.neighbors.pop(index)
self.f_values.pop(index)

self.neighbor()


For the pythonic ways, I'd recommend using """ for documenting functions and methods.

#  this method gives you the f_value off all the neighbors
def f_value(self):
...


I'd go with

def f_value(self):
'''
This method gives you the f_value off all the neighbors.
'''
...


This allow you to write things like:

def return_6(self):
'''
This method return 6.
>>>return_6()
6
'''
return 6


Then run automatised test using your docstring or writing this:

def random_number_generator(arg1, arg2):
"""
Summary line.

Extended description of function.

Parameters
----------
arg1 : int
Description of arg1
arg2 : str
Description of arg2

Returns
-------
int
Description of return value

"""
return 42


And then use tools like Sphinx to build your documentation with ReadTheDoc for example.