I'm trying to implement breadth first search in python. I'm trying to find a path through a grid, starting from one square and finding a path towards the goal square. There are obstacles throughout the grid marked by the character 'o'. The "graph" refers to a two dimensional array of Nodes, a simple class:
# Node class
class Node:
def __init__(self, val, pos):
self.val = val
self.pos = pos
self.visited = False
def __str__(self):
return "%s" % self.val
I realize this isn't the cleanest implementation of BFS--I don't have much experience working with python, so sometimes I had to use repetitive code because I wasn't sure how python handles the pointers beneath some of the local variables. Anyway, this BFS loops infinitely and I can't figure out why. Any help would be appreciated!
The fringe is basically a queue, and before moving a level deeper, each node's adjacent squares are checked in the order of left, up, right, and down.
# Breadth First Search
def bfs(arena, start):
# fringe implemented as a FIFO list (behaves like a queue)
fringe = []
fringe.append([start])
start.visited = True
while fringe:
# get the first path from the fringe queue
path = fringe.pop(0)
print "pop!"
# get the last node from the path
node = path[-1]
# goal check
if node.val == 'g':
print "PATH FOUND!!"
return path
# get all adjacent nodes, construct a new path and push it to the fringe queue
pos = node.pos
# get left node first
if pos[1]-1>=0:
neighbor = graph[pos[0]][pos[1]-1]
newPath = path[:]
if neighbor.val == 'o':
neighbor.visited = True
graph[pos[0]][pos[1]-1].visited = True
if neighbor is not neighbor.visited:
neighbor.visited = True
graph[pos[0]][pos[1]-1].visited = True
newPath.append(neighbor)
fringe.append(newPath)
print "left node added!"
# get node above current node
if pos[0]-1>=0:
neighbor = graph[pos[0]-1][pos[1]]
newPath = path[:]
if neighbor.val == 'o':
neighbor.visited = True
graph[pos[0-1]][pos[1]].visited = True
if neighbor is not neighbor.visited:
neighbor.visited = True
graph[pos[0-1]][pos[1]].visited = True
newPath.append(neighbor)
fringe.append(newPath)
print "top noded added!"
# get node to the right of current node
if pos[1]+1 < columns:
neighbor = graph[pos[0]][pos[1]+1]
newPath = path[:]
if neighbor.val == 'o':
neighbor.visited = True
graph[pos[0]][pos[1]+1].visited = True
if neighbor is not neighbor.visited:
neighbor.visited = True
graph[pos[0]][pos[1]+1].visited = True
newPath.append(neighbor)
fringe.append(newPath)
print "right node added!"
# get node below current node
if pos[0]+1 < rows:
neighbor = graph[pos[0]+1][pos[1]]
newPath = path[:]
if neighbor.val == 'o':
neighbor.visited = True
graph[pos[0]+1][pos[1]].visited = True
if neighbor is not neighbor.visited:
neighbor.visited = True
graph[pos[0]+1][pos[1]].visited = True
newPath.append(neighbor)
fringe.append(newPath)
print "node below added!"
Here is working code, please carefully read the comments.
from pprint import pprint
# pos is (row, column), not (x, y)
class Node:
def __init__(self, val, pos):
self.val = val
# Position info is stored here and ALSO as index in graph -
# this is a form of data duplication, which is evil!
self.pos = pos
self.visited = False
def __repr__(self):
# nice repr for pprint
return repr(self.pos)
# You had mistake here, "arena" instead of "graph"
# Before posting questions on stackoverflow, make sure your examples
# at least produce some incorrect result, not just crash.
# Don't make people fix syntax errors for you!
def bfs(graph, start):
fringe = [[start]]
# Special case: start == goal
if start.val == 'g':
return [start]
start.visited = True
# Calculate width and height dynamically. We assume that "graph" is dense.
width = len(graph[0])
height = len(graph)
# List of possible moves: up, down, left, right.
# You can even add chess horse move here!
moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while fringe:
# Print fringe at each step
pprint(fringe)
print('')
# Get first path from fringe and extend it by possible moves.
path = fringe.pop(0)
node = path[-1]
pos = node.pos
# Using moves list (without all those if's with +1, -1 etc.) has huge benefit:
# moving logic is not duplicated. It will save you from many silly errors.
# The example of such silly error in your code:
# graph[pos[0-1]][pos[1]].visited = True
# ^^^
# Also, having one piece of code instead of four copypasted pieces
# will make algorithm much easier to change (e.g. if you want to move diagonally).
for move in moves:
# Check out of bounds. Note that it's the ONLY place where we check it. Simple and reliable!
if not (0 <= pos[0] + move[0] < height and 0 <= pos[1] + move[1] < width):
continue
neighbor = graph[pos[0] + move[0]][pos[1] + move[1]]
if neighbor.val == 'g':
return path + [neighbor]
elif neighbor.val == 'o' and not neighbor.visited:
# In your original code there was a line:
# if neighbor is not neighbor.visited:
# which was completely wrong. Read about "is" operator.
neighbor.visited = True
fringe.append(path + [neighbor]) # creates copy of list
raise Exception('Path not found!')
if __name__ == '__main__':
# Graph in convenient form: 0 is empty, 1 is wall, 2 is goal.
# Note that you can have multiple goals.
graph = [
[0, 1, 0, 1],
[0, 0, 0, 0],
[0, 1, 0, 1],
[0, 0, 2, 1]
]
# Transform int matrix to Node matrix.
TRANSLATE = {0: 'o', 1: 'x', 2: 'g'}
graph = [[Node(TRANSLATE[x], (i, j)) for j, x in enumerate(row)] for i, row in enumerate(graph)]
# Find path
try:
path = bfs(graph, graph[0][0])
print("Path found: {!r}".format(path))
except Exception as ex:
# Learn to use exceptions. In your original code, "no path" situation
# is not handled at all!
print(ex)
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
If anyone is interested, I have posted this question to Code Review with a bounty.
View it here
This is not a traditional maze where you find the shortest path (like the gif on the left). In order to solve it, you need to visit every available node before reaching the end, where once a node is visited it turns into a wall (like the gif on the right).
My current solution works quickly for smaller mazes or ones with a lot of walls, such as this, usually finding a path within a couple seconds. But it takes a lot longer as the size of the maze increases or has more open space, such as this (nearly 5 minutes to find a path). Ideally I would like solve mazes up to a 15x20 size in ~30 seconds.
Here is an overview:
Input the maze (2D list of Tile objects), start node , and end node to the MazeSolver class.
A neighboring node is chosen (up, right, down, left).
If that node is_open(), then check if it is_safe() to visit. A node is safe if visiting it will not obstruct our path to any other open node in the maze. This involves an A* search from that node to every other open node, to quickly check if the path exists (every node returned in the path can be skipped for its own search to reduce the number of A* searches).
If it is_safe(), visit the node and link their next and prev attributes.
If the node is not open or not safe, add it to the closed list.
If all 4 neighbors are in closed, backtrack to the previous node.
Repeat 2-6 until end is reached, return the path if found.
At this point I am unsure how to improve the algorithm. I am aware of techniques like cython to speed up the execution time of the your code, but my real goal is to add some logic to make the solution smarter and faster. (Although feel free to recommend these techniques as well, I don't imagine multiprocessing could work here?).
I believe adding some logic as to how a neighbor is chosen may be the next step. Currently, a direction is picked from the list MazeSolver.map, and is used until the neighbor in that direction is not open. Then the next one in the list is chosen, and it just cycles through in order. So there is no intelligent decision making for choosing the neighbor.
Many path finding algorithms assign weights and scores, but how can I tell if one neighbor is more important now than another? The start and end positions can be anywhere in the maze, and you have to visit every node, so the distance to the end node seems insignificant. Or is there a way to predict that a node is not safe without having to do an A* search with each other node? Perhaps separating the maze into smaller chunks and then combining them afterwards would make a difference? All suggestions are welcome, even an entirely new method of solving.
Here is the code.
class Tile:
def __init__(self, row, column):
self.position = (row, column)
self.mode = 1 # 1 = open, 0 = closed (wall)
self.next = self.prev = None
self.closed = []
def __add__(self, other):
return (self.position[0] + other[0], self.position[1] + other[1])
class MazeSolver:
def __init__(self, maze, start, end):
self.maze = maze
self.h, self.w = len(maze) - 1, len(maze[0]) - 1
self.start = maze[start[0]][start[1]]
self.end = maze[end[0]][end[1]]
self.current = self.start
self.map = [(-1, 0), (0, 1), (1, 0), (0, -1)] # Up, right, down, left
def solve(self):
i = 0
while self.current != self.end:
node = self.current + self.map[i]
if self.is_open(node):
if self.is_safe(node):
# Link two nodes like a Linked List
self.current.next = self.maze[node[0]][node[1]]
self.current.next.prev = self.current
self.current.mode -= 1
self.current = self.current.next
continue
else:
self.current.closed.append(node)
else:
i += 1 if i < 3 else -3 # Cycle through indexes in self.map
if len(self.current.closed) == 4:
if self.current == self.start:
# No where to go from starting node, no path exists.
return 0
self.current.closed = []
self.current = self.current.prev
self.current.mode += 1
self.current.closed.append(self.current.next.position)
return self.get_path()
def is_open(self, node):
'''Check if node is open (mode = 1)'''
if node in self.current.closed:
return 0
elif any([node[0]>self.h, node[0]<0, node[1]>self.w, node[1]<0]):
# Node is out of bounds
self.current.closed.append(node)
return 0
elif self.maze[node[0]][node[1]].mode == 0:
self.current.closed.append(node)
return self.maze[node[0]][node[1]].mode
def is_safe(self, node):
'''Check if path is obstructed when node is visitied'''
nodes = [t.position for row in self.maze for t in row if t.mode > 0]
nodes.remove(self.current.position)
# Sorting positions by greatest manhattan distance (which reduces calls to astar)
# decreases solve time for the small maze but increases it for the large maze.
# Thus at some point the cost of sorting outweighs the benefit of fewer A* searches.
# So I have left it commented out:
#nodes.sort(reverse=True, key=lambda x: abs(node[0] - x[0]) + abs(node[1] - x[1]))
board = [[tile.mode for tile in row] for row in self.maze]
board[self.current.position[0]][self.current.position[1]] = 0
checked = []
for goal in nodes:
if goal in checked:
continue
sub_maze = self.astar(board, node, goal)
if not sub_maze:
return False
else:
checked = list(set(checked + sub_maze))
return True
def astar(self, maze, start, end):
'''An implementation of the A* search algorithm'''
start_node = Node(None, start)
end_node = Node(None, end)
open_list = [start_node]
closed_list = []
while len(open_list) > 0:
current_node = open_list[0]
current_index = 0
for index, item in enumerate(open_list):
if item.f < current_node.f:
current_node = item
current_index = index
open_list.pop(current_index)
closed_list.append(current_node)
if current_node == end_node:
path = []
current = current_node
while current is not None:
path.append(current.position)
current = current.parent
return path
children = []
for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1])
if node_position[0] > (len(maze) - 1) or node_position[0] < 0 or node_position[1] > (len(maze[0]) -1) or node_position[1] < 0:
continue
if maze[node_position[0]][node_position[1]] == 0:
continue
new_node = Node(current_node, node_position)
children.append(new_node)
for child in children:
if child in closed_list:
continue
child.g = current_node.g + 1
child.h = ((child.position[0] - end_node.position[0])**2) + ((child.position[1] - end_node.position[1])**2)
child.f = child.g + child.h
if child in open_list:
if child.g > open_list[open_list.index(child)].g:
continue
open_list.append(child)
return []
def get_path(self):
path = []
pointer = self.start
while pointer is not None:
path.append(pointer.position)
pointer = pointer.next
return path
class Node:
'''Only used by the MazeSolver.astar() function'''
def __init__(self, parent=None, position=None):
self.parent = parent
self.position = position
self.g = self.h = self.f = 0
def __eq__(self, other):
return self.position == other.position
If you would like to run the mazes from the pictures I linked above (Small, Large):
import time
def small_maze():
maze = [[Tile(r, c) for c in range(11)] for r in range(4)]
maze[1][1].mode = 0
for i in range(11):
if i not in [3, 8]:
maze[3][i].mode = 0
return maze
def large_maze():
maze = [[Tile(r, c) for c in range(15)] for r in range(10)]
for i in range(5, 8):
maze[4][i].mode = 0
maze[5][5].mode = maze[5][7].mode = maze[6][5].mode = 0
return maze
C = MazeSolver(small_maze(), (3, 8), (3, 3)) #(large_maze(), (2, 2), (5, 6))
t = time.time()
path = C.solve()
print(round(time.time() - t, 2), f'seconds\n\n{path}')
# The end node should always have some walls around it to avoid
# the need to check if it was reached prematurely.
I am currently working on solving a train schedule optimization problem as part of my studies. In this problem, a utility function has to be maximized which increases in the number of (critical) stations visited, and decreases in the amount of trains used and in the total amount of minutes the trains are running.
The problem consists of stations (nodes) and connections (edges). Data on both of these is first loaded from two CSV files. Then, classes are instantiated for each station (containing the name and whether or not it is critical), and each connection (containing the stations in the connection, and the time it costs to travel to one another). These stations and connection are both stored in dictionaries.
As a first step, my groupmates and I decided we first wanted to implement a version of Dijkstra's Pathfinding Algorithm in order to find the quickest route between two stations. BogoToBogo has a very detailed guide on how to implement a version of Dijkstra's algorithm. We decided first to try and implement their code to see what the results would be. However, a TypeError keeps popping up:
TypeError: '<' not supported between instances of 'Vertex' and 'Vertex'
If anyone has an idea what is causing this error, any help would be greatly appriciated!
#Makes the shortest path from v.previous
def shortest(v, path):
if v.previous:
path.append(v.previous.get_id())
shortest(v.previous, path)
return
def dijkstra(aGraph, start, target):
print('Dijkstras shortest path')
# Set the distance for the start node to zero
start.set_distance(0)
# Put tuple pair into the priority queue
unvisited_queue = [(v.get_distance(),v) for v in aGraph]
heapq.heapify(unvisited_queue)
while len(unvisited_queue):
# Pops a vertex with the smallest distance
uv = heapq.heappop(unvisited_queue)
current = uv[1]
current.set_visited()
#for next in v.adjacent:
for next in current.adjacent:
# if visited, skip
if next.visited:
continue
new_dist = current.get_distance() + current.get_weight(next)
if new_dist < next.get_distance():
next.set_distance(new_dist)
next.set_previous(current)
print('updated : current = ' + current.get_id() + ' next = ' + next.get_id() + ' new_dist = ' + next.get_distance())
else:
print('not updated : current = ' + current.get_id() + ' next = ' + next.get_id() + ' new_dist = ' + next.get_distance())
# Rebuild heap
# 1. Pop every item
while len(unvisited_queue):
heapq.heappop(unvisited_queue)
# 2. Put all vertices not visited into the queue
unvisited_queue = [(v.get_distance(),v) for v in aGraph if not v.visited]
heapq.heapify(unvisited_queue)
if __name__ == "__main__":
# Calling the CSV loading functions in mainActivity
# These functions will also instantiate station and connections objects
load_stations(INPUT_STATIONS)
load_connections(INPUT_CONNECTIONS)
g = Graph()
for index in stations:
g.add_vertex(stations[index].name)
for counter in connections:
g.add_edge(connections[counter].stat1, connections[counter].stat2, int(connections[counter].time))
for v in g:
for w in v.get_connections():
vid = v.get_id()
wid = w.get_id()
print( vid, wid, v.get_weight(w))
dijkstra(g, g.get_vertex('Alkmaar'), g.get_vertex('Zaandam'))
target = g.get_vertex('Zaandam')
path = [target.get_id()]
shortest(target, path)
print('The shortest path :' + (path[::-1]))
In this case, the function dijkstra is called, given the parameters g (which is a instance of the Graph class), Alkmaar, and Zaandam.
# Represents a grid of nodes/stations composed of nodes and edges
class Graph:
def __init__(self):
self.vert_dict = {}
self.num_vertices = 0
def __iter__(self):
return iter(self.vert_dict.values())
def add_vertex(self, node):
self.num_vertices = self.num_vertices + 1
new_vertex = Vertex(node)
self.vert_dict[node] = new_vertex
return new_vertex
def get_vertex(self, n):
if n in self.vert_dict:
return self.vert_dict[n]
else:
return None
def add_edge(self, frm, to, cost = 0):
if frm not in self.vert_dict:
self.add_vertex(frm)
if to not in self.vert_dict:
self.add_vertex(to)
self.vert_dict[frm].add_neighbor(self.vert_dict[to], cost)
self.vert_dict[to].add_neighbor(self.vert_dict[frm], cost)
def get_vertices(self):
return self.vert_dict.keys()
def set_previous(self, current):
self.previous = current
def get_previous(self, current):
return self.previous
The Graph class.
# Represents a node (station)
class Vertex:
def __init__(self, node):
self.id = node
self.adjacent = {}
# Set distance to infinity for all nodes
self.distance = sys.maxsize
# Mark all nodes unvisited
self.visited = False
# Predecessor
self.previous = None
def add_neighbor(self, neighbor, weight=0):
self.adjacent[neighbor] = weight
def get_connections(self):
return self.adjacent.keys()
def get_id(self):
return self.id
def get_weight(self, neighbor):
return self.adjacent[neighbor]
def set_distance(self, dist):
self.distance = dist
def get_distance(self):
return self.distance
def set_previous(self, prev):
self.previous = prev
def set_visited(self):
self.visited = True
def __str__(self):
return str(self.id) + ' adjacent: ' + str([x.id for x in self.adjacent])
The Vertex class.
Thanks for your time!
I think this might help, but the way when posting to stackoverflow just post as little and complete information as possible
# Put tuple pair into the priority queue
unvisited_queue = [(v.get_distance(),v) for v in aGraph]
heapq.heapify(unvisited_queue)
if you look at this code it converts the list to a heap which requires < comparison of whatever you give to it, define a __gt__() method in the vertex class , the function will determine what gets popped first so write it as you see fit and I think the error will go away. :-)
I was looking through the code written by Ben Langmead on SuffixTrees. I am having a hard time figuring out how to print all the edges of the suffix tree. What is a way to store them in a set and save it in the object class?
class SuffixTree(object):
class Node(object):
def __init__(self, lab):
self.lab = lab # label on path leading to this node
self.out = {} # outgoing edges; maps characters to nodes
def __init__(self, s):
""" Make suffix tree, without suffix links, from s in quadratic time
and linear space """
suffix=[]
self.suffix=suffix
self.root = self.Node(None)
self.root.out[s[0]] = self.Node(s) # trie for just longest suf
# add the rest of the suffixes, from longest to shortest
for i in xrange(1, len(s)):
# start at root; we’ll walk down as far as we can go
cur = self.root
j = i
while j < len(s):
if s[j] in cur.out:
child = cur.out[s[j]]
lab = child.lab
# Walk along edge until we exhaust edge label or
# until we mismatch
k = j+1
while k-j < len(lab) and s[k] == lab[k-j]:
k += 1
if k-j == len(lab):
cur = child # we exhausted the edge
suffix.append(child.lab)
j = k
else:
# we fell off in middle of edge
cExist, cNew = lab[k-j], s[k]
# create “mid”: new node bisecting edge
mid = self.Node(lab[:k-j])
mid.out[cNew] = self.Node(s[k:])
# original child becomes mid’s child
mid.out[cExist] = child
# original child’s label is curtailed
child.lab = lab[k-j:]
# mid becomes new child of original parent
cur.out[s[j]] = mid
else:
# Fell off tree at a node: make new edge hanging off it
cur.out[s[j]] = self.Node(s[j:])
def followPath(self, s):
""" Follow path given by s. If we fall off tree, return None. If we
finish mid-edge, return (node, offset) where 'node' is child and
'offset' is label offset. If we finish on a node, return (node,
None). """
cur = self.root
i = 0
while i < len(s):
c = s[i]
if c not in cur.out:
return (None, None) # fell off at a node
child = cur.out[s[i]]
lab = child.lab
j = i+1
while j-i < len(lab) and j < len(s) and s[j] == lab[j-i]:
j += 1
if j-i == len(lab):
cur = child # exhausted edge
i = j
elif j == len(s):
return (child, j-i) # exhausted query string in middle of edge
else:
return (None, None) # fell off in the middle of the edge
return (cur, None) # exhausted query string at internal node
def hasSubstring(self, s):
""" Return true iff s appears as a substring """
node, off = self.followPath(s)
return node
def hasSuffix(self, s):
""" Return true iff s is a suffix """
node, off = self.followPath(s)
if node is None:
return False # fell off the tree
if off is None:
# finished on top of a node
return '$' in node.out
else:
# finished at offset 'off' within an edge leading to 'node'
return node.lab[off] == '$'
I have a project to make a linked list in python.
My program needs to add to the list, remove from it and get elements. Sounds easy right? Wrong! We aren't allowed to use normal lists or built-in functions (other than the basic print, str...)
I have one problem with my code, I have to initialise a blank list then add the elements, 1 by 1. Everything else works fine.
My questions:
Is this how a normal python list works?
Is it possible to add items to a linked list with a loop? (without another list)
Here's the code:
class Node: # the node class
def __init__(self, cargo = None, next = None): # __init__ stands for initialize
self.cargo = cargo # e.g. steve
self.next = next # represents the next node or None if its the last
def __str__(self): # __str__ is called when the node is printed or converted to a string
return str(self.cargo) # return a string
class List: # the main list class
def __init__(self): # default is to initialize an empty list
self.first_node = None
self.last_node = None
self.length = 0
def get(self, position, length): # function for efficiency
if position == "end":
position = length - 1 # last
if position > length - 1: # can't go beyond last
raise ValueError("List index out of range")
prv_node = self.first_node
node = self.first_node # start at the first
num = 0
while num < position: # go up to position
prv_node = node # remember the previous node
node = node.next # next node!
num = num + 1
return prv_node, node, position
def add_node(self, cargo, position = "end"): # adds a node
prv_node, node, position = self.get(position, self.length + 1) # +1 because the length is being increased
print("adding node at "+str(position)+": "+str(cargo))
if position == 0: # first
self.first_node = Node(cargo, next = self.first_node) # the first node is the new node
if self.length == 0: # first node to be added
self.last_node = self.first_node # there is only one node
elif position == self.length: # last
self.last_node.next = Node(cargo, next = None) # last_node.next was None, it is now a new node
self.last_node = self.last_node.next # last node is now the new last node
else: # normal
prv_node.next = Node(cargo, next = node) # stick it in between
self.length = self.length + 1 # length is now + 1
def get_node(self, position): # gets a node
...
def remove_node(self, position): # removes a node
...
def __str__(self): # when the list is printed
node = self.first_node # start from the first
string = ""
while node != self.last_node: # go to the end
string = string + str(node) + ", " # print each node
node = node.next
string = string + str(self.last_node) # last node hasn't been added yet
return string
# initialize
mylist = List()
mylist.add_node("steve")
mylist.add_node("james")
mylist.add_node("tom")
mylist.add_node("david")
mylist.add_node("hoe-yin")
mylist.add_node("daniel")
print(mylist)
[EDIT] second question re-phrased
Here's how Python lists are implemented in CPython: http://www.laurentluce.com/posts/python-list-implementation/
If you have your values in some other iterable, then yes:
for item in list_of_items:
mylist.add_node(item)
I have a problem with my attempt at A* - this does not happen every time it runs, but every so often it makes it so two cells become each other's parents.
This in turn leads to an infinite loop when I try to recreate the path. I've been trying to find out why this happens for some time now, but can't figure it out, so any help would be appreciated.
def find_path(self, start, end):
tstart = time.time()
count = 0
evaled = set()
notEvaled = set([start])
start.g_score(start)
start.h_score(end)
start.f_score()
while len(notEvaled) != 0:
current = min(notEvaled, key=attrgetter('f'))
notEvaled.remove(current)
for neighbour in current.neighbours:
if neighbour.full or neighbour in evaled: continue
if neighbour in notEvaled:
if neighbour.parent != None and neighbour.parent.g > current.g:
neighbour.parent = current
neighbour.g_score(start)
neighbour.f_score()
else:
neighbour.parent = current
neighbour.g_score(start)
neighbour.h_score(end)
neighbour.f_score()
notEvaled.add(neighbour)
if neighbour == end:
path = []
while end != None:
path.insert(0, end)
end = end.parent
return path
evaled.add(current)
return None
Here are my score function, but i doubt they matter
def g_score(self, start):
if self == start:
self.g = 0
else:
self.g = self.parent.g + 1
def h_score(self, end):
self.h = abs(end.x - self.x) + abs(end.y - self.y)
def f_score(self):
self.f = self.g + self.h
Before I begin: the common naming convention is openSet/closedSet instead of evaled/notEvaled. The naming convention you use becomes confusing when you need to use a double negative, as in: aNode not in notEvaled. So, in my answer I use the openSet/closedSet convension.
One thing that stands out to me is you add the current node to the closedSet (evaled) immediately after removing it from the openSet (notEvaled).
The second thing that stands out is that you check whether a node is the endpoint before adding it to the openSet. While this may seem like an optimization, it actually forces you to perform the check on every single opened node, rather than on the subset of open nodes that are actually visited.
Here is an adaptation of your A* algorithm that should work as expected. It is based on the pseudo code from Wikipedia except that it integrates the reconstruct_path method:
def find_path(self, start, end):
tstart = time.time()
count = 0
closedSet = set()
openSet = set([start])
start.g_score(start)
start.h_score(end)
start.f_score()
while len(openSet) != 0:
current = min(openSet, key=attrgetter('f'))
openSet.remove(current)
closedSet.add(current)
#Check if goal is reached
if(current == end):
#Construct path from start to goal
path = []
while end != None:
path.insert(0,end)
end = end.parent
return path
#Open neighbors
for neighbour in current.neighbours:
if neighbour.full: continue
if neighbour in closedSet: continue
tentative_g_score = 1 + current.g
if neighbour not in openSet or tentative_g_score < neighbor.g:
neighbour.parent = current
neighbour.g = tentative_g_score
neighbour.f_score()
if neighbour not in openSet:
openSet.add(neighbour)
return None