A* Algorithm does not find shortest path - python

I am trying to implement the A* algorithm in python but have hit a problem when trying to find the path of this map:
X X X X X X X S = Start
0 0 0 X 0 0 0 E = End
0 S 0 X 0 E 0 X = Wall
0 0 0 X 0 0 0
0 0 0 0 0 0 0
I am using the Manhattan method. My implementation does find a path, but not the shortest one. The error starts on it's second move -- after moving right. At this point it can move up and the heuristic cost would be four (three right, one down) or down (three right, one up). Is there a way so it chooses down to get the shortest path?
Code:
class Node:
def __init__(self, (x, y), g, h, parent):
self.x = x
self.y = y
self.g = g
self.h = h
self.f = g+h
self.parent = parent
def __eq__(self, other):
if other != None:
return self.x == other.x and self.y == other.y
return False
def __lt__(self, other):
if other != None:
return self.f < other.f
return False
def __gt__(self, other):
if other != None:
return self.f > other.f
return True
def __str__(self):
return "(" + str(self.x) + "," + str(self.y) + ") " + str(self.f)
def find_path(start, end, open_list, closed_list, map, no_diag=True, i=1):
closed_list.append(start)
if start == end or start == None:
return closed_list
new_open_list = []
for x, y in [(-1,1),(-1,-1),(1,-1),(1,1),(0,-1),(0,1),(-1,0),(1,0)]:
full_x = start.x + x
full_y = start.y + y
g = 0
if x != 0 and y != 0:
if no_diag:
continue
g = 14
else:
g = 10
h = 10 * (abs(full_x - end.x) + abs(full_y - end.y))
n = Node((full_x,full_y),g,h,start)
if 0 <= full_y < len(map) and 0 <= full_x < len(map[0]) and map[full_y][full_x] != 1 and n not in closed_list:
if n in open_list:
if open_list[open_list.index(n)].g > n.g:
new_open_list.append(n)
else:
new_open_list.append(open_list[open_list.index(n)])
else:
new_open_list.append(n)
if new_open_list == None or len(new_open_list) == 0:
return find_path(start.parent, end, open_list, closed_list, map, no_diag, i-1)
new_open_list.sort()
return find_path(new_open_list[0], end, new_open_list, closed_list, map, no_diag)

You seem to be constructing a new open list for each node, which contains only that node's neighbors. This essentially makes your search a form of a depth-first search, while A* should be a best-first search.
You need to use one open list which will be updated with each node's neighbors as you visit that node. The old nodes in the open list must remain there until they are traversed and moved to the closed list.
Regarding what you said in your question, It's OK for the search to try moving up before down (since according to the heuristic, they are the same distance from the goal). What matters is that in the end, the path chosen will be the shortest.

Related

Speeding up A* pathfinding for a massive grid

I am currently trying to fix my pathfinding system in my game. The A pathfinding python code is super slow, considering it has to calculate thousands of nodes each time for my grid. My grid is stored in a dictionary that has the positions of all of the walls and obstacles. Are there any ways I could speed this up signifigantly?
Here is my algorithm:
def findpath_subgrid(self, start, end):
self.subgrid_cache.clear()
start_subgrid = (start[0] // self.subgrid_size, start[1] // self.subgrid_size)
end_subgrid = (end[0] // self.subgrid_size, end[1] // self.subgrid_size)
if start_subgrid == end_subgrid:
return self.find_path(start, end)
else:
with self.lock:
if start_subgrid in self.subgrid_cache:
return self.subgrid_cache[start_subgrid]
else:
path = self.find_path(start, end)
self.subgrid_cache[start_subgrid] = path
return path
def heuristic(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def find_path(self, start, end):
queue = []
heapq.heappush(queue, (0, start))
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while queue:
current = heapq.heappop(queue)[1]
if current == end:
break
for next in self.adjacent_cells(current):
new_cost = cost_so_far[current] + self.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + self.heuristic(end, next)
heapq.heappush(queue, (priority, next))
came_from[next] = current
return self.reconstruct_path(came_from, start, end)
def adjacent_cells(self, pos):
x, y = pos
results = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
results = filter(self.in_bounds, results)
results = filter(self.passable, results)
return results
def in_bounds(self, pos):
x, y = pos
return 0 <= x < 2000 and 0 <= y < 2000
def passable(self, pos):
return self.grid.get(pos) != 1 # check if the cell is not an obstacle using the new grid dictionary
def cost(self, current, next):
if self.grid.get(next) == 2:
return 1000 # high cost for cells with enemies
else:
return 1 # otherwise, the cost is 1
def heuristic(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def reconstruct_path(self, came_from, start, goal):
current = goal
path = []
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
I've tried subgrids, cache's but its still very very slow.
If a position is extracted from the queue, you do not need to consider them again in your search (this statement is true because you are working on a grid and using manhattan distance as heuristic function). I added a variable expanded_list to function find_path and adjacent_cells (my Python writing is not the best so you can write them better later). Also it would be better if you use a 2D array for holding your grid instead of a dictionary.
def find_path(self, start, end):
queue = []
heapq.heappush(queue, (0, start))
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
expanded_list = {}
expanded_list[current] = False
while queue:
current = heapq.heappop(queue)[1]
expanded_list[current] = True
if current == end:
break
for next in self.adjacent_cells(current):
expanded_list[next] = False
new_cost = cost_so_far[current] + self.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + self.heuristic(end, next)
heapq.heappush(queue, (priority, next))
came_from[next] = current
return self.reconstruct_path(came_from, start, end)
def adjacent_cells(self, pos, expanded_list):
x, y = pos
results = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
results = filter(self.in_bounds, results)
results = filter(self.passable, results)
results = filter(lambda pos:not(pos in expanded_list and expanded_list[pos]==True), results)
return results

How do I create a Set method for a class with a variable-dimension container?

If I have a list class that can be initialized with a variable number of dimensions, how do I set an entry at the lowest level of the list with an element? (Also would like to know if my get method should work in theory)
I'm trying to simulate board games that use multiple dimensions (Can you even imagine 5-th dimensional chess? How about 17th?)
class Board():
DIMENSIONS = [8, 8]
#board and pieces have their respective rules.
def __init__(self, D=[8,8]):
if len(D) <= 0:
board = [None for i in range(D)]
else:
board = [None for i in range(D[0])]
for j in range(1,len(D)):
board = [board for i in range(D[j])]
def get(self, location):
try:
for coordinate in location:
result = board[coordinate]
return result
except:
print('Error: Cannot get coordinate')
return None
def set(self, location, piece):
try:
for coordinate in location:
result = self.board[coordinate]
result = piece
except:
print('Error: Cannot set coordinate')
def move(self, start, end):
x = self.get(start)
if x is not None:
for m, r in x.moves, x.rules:
if self.get(is_legitimate(self, start, m, r)) == end:
= x
pass
#Check alignment and position, if it's transformable react according to board rules.
#returns false if not, but returns location if legit.
def is_legitimate(self, start, move, rule):
location = start
forwardback = True
while move != 1:
primes = [2]
while move % primes[-1] == 0:
if forwardback:
location[len(primes) // 2]+=1
else:
location[len(primes) // 2]-=1
move = move % primes[-1]
if not self.bounds(location):
return False
primes.append(next_prime(primes))
forwardback = not forwardback
def bounds(self, location):
for coordinate, d in location, self.DIMENSIONS:
if coordinate < 0 or coordinate > d:
return False
return True
#using prime numbers?
def next_prime(primes):
if len(primes) == 0:
return 2
prev_result = 1
result = 2
while prev_result != result:
prev_result = result
for x in primes:
if result == x or result % x == 0:
result += 1
break
return result
Code is mostly rough draft, don't play just look.

How to pretty print a quadtree in python?

I have some code that can make a quad tree from data points. I know how to print out binary tree with slashes, but I don't even know where to start to print/draw out a tree with 4 children instead of 2 each to be able to visualize my tree.
I've been testing it by using my search_pqtreee function. For example, to list all the points in the northeast quadrant, I can test it by making a list like: [search_pqtree(q.ne,p) for p in points]
#The point import is a class for points in Cartesian coordinate systems
from point import *
class PQuadTreeNode():
def __init__(self,point,nw=None,ne=None,se=None,sw=None):
self.point = point
self.nw = nw
self.ne = ne
self.se = se
self.sw = sw
def __repr__(self):
return str(self.point)
def is_leaf(self):
return self.nw==None and self.ne==None and \
self.se==None and self.sw==None
def search_pqtree(q, p, is_find_only=True):
if q is None:
return
if q.point == p:
if is_find_only:
return q
else:
return
dx,dy = 0,0
if p.x >= q.point.x:
dx = 1
if p.y >= q.point.y:
dy = 1
qnum = dx+dy*2
child = [q.sw, q.se, q.nw, q.ne][qnum]
if child is None and not is_find_only:
return q
return search_pqtree(child, p, is_find_only)
def insert_pqtree(q, p):
n = search_pqtree(q, p, False)
node = PQuadTreeNode(point=p)
if p.x < n.point.x and p.y < n.point.y:
n.sw = node
elif p.x < n.point.x and p.y >= n.point.y:
n.nw = node
elif p.x >= n.point.x and p.y < n.point.y:
n.se = node
else:
n.ne = node
def pointquadtree(data):
root = PQuadTreeNode(point = data[0])
for p in data[1:]:
insert_pqtree(root, p)
return root
#Test
data1 = [ (2,2), (0,5), (8,0), (9,8), (7,14), (13,12), (14,13) ]
points = [Point(d[0], d[1]) for d in data1]
q = pointquadtree(points)
print([search_pqtree(q.ne, p) for p in points])
What I'm trying to say is if I was pretty printing a binary tree, it might look like this:
(2, 2)
/ \
(0, 5) (8, 0)
/ \ / \
Is there a way to write a function that print out 4 lines each? Or maybe print it out sideways?
As you classified your question with GIS and spatial, this problem made me think of a map with north-east, north-west, south-east and south-west in each corner.
A single node quadtree would simply be :
(0,0)
A two node quadtree would be :
.|( 1, 1)
----( 0, 0)----
.|.
With 3 nodes in depth that would go to :
| .|( 2, 2)
|----( 1, 1)----
.| .|.
------------( 0, 0)------------
.|.
|
|
I've implemented this idea, with some changes to your code to make it easier:
I've added a trivial point class, with the __repr__ method I needed for number formatting
I made quadrants into a dictionary to be able to loop on them
I thought I would need the get_depth method, but it's not used...
I also think that search and insert functions should be methods of the class PQuadTreeNode, but I leave it to you as an exercise :)
The implementation works with the following steps:
If the quadtree is a leaf, its map is the central point
Get the maps of the 4 quadrants (if empty, it's a dot)
Normalize them using the size of the largest, and puts them near the center of the parent
Combine the 4 quadrants with the quadtree point at the center.
This is of course higly recursive, and I didn't made any attempt at optimization.
If numbers have a length greater than 2 (like 100 or -10), you can adjust the num_length variable.
num_length = 2
num_fmt = '%' + str(num_length) + 'd'
class Point():
def __init__(self,x=None,y=None):
self.x = x
self.y = y
def __repr__(self):
return '(' + (num_fmt % self.x) + ',' + (num_fmt % self.y) + ')'
def normalize(corner, quadmap, width, height):
old_height = len(quadmap)
old_width = len(quadmap[0])
if old_height == height and old_width == width:
return quadmap
else:
blank_width = width - old_width
if corner == 'nw':
new = [' '*width for i in range(height - old_height)]
for line in quadmap:
new.append(' '*blank_width + line)
elif corner == 'ne':
new = [' '*width for i in range(height - old_height)]
for line in quadmap:
new.append(line + ' '*blank_width)
elif corner == 'sw':
new = []
for line in quadmap:
new.append(' '*blank_width + line)
for i in range(height - old_height):
new.append(' '*width)
elif corner == 'se':
new = []
for line in quadmap:
new.append(line + ' '*blank_width)
for i in range(height - old_height):
new.append(' '*width)
return new
class PQuadTreeNode():
def __init__(self,point,nw=None,ne=None,se=None,sw=None):
self.point = point
self.quadrants = {'nw':nw, 'ne':ne, 'se':se, 'sw':sw}
def __repr__(self):
return '\n'.join(self.get_map())
def is_leaf(self):
return all(q == None for q in self.quadrants.values())
def get_depth(self):
if self.is_leaf():
return 1
else:
return 1 + max(q.get_depth() if q else 0 for q in self.quadrants.values())
def get_map(self):
if self.is_leaf():
return [str(self.point)]
else:
subquadmaps = {
sqn:sq.get_map() if sq else ['.']
for sqn, sq
in self.quadrants.items()
}
subheight = max(len(map) for map in subquadmaps.values())
subwidth = max(len(mapline) for map in subquadmaps.values() for mapline in map)
subquadmapsnorm = {
sqn:normalize(sqn, sq, subwidth, subheight)
for sqn, sq
in subquadmaps.items()
}
map = []
for n in range(subheight):
map.append(subquadmapsnorm['nw'][n] + '|' + subquadmapsnorm['ne'][n])
map.append('-' * (subwidth-num_length-1) + str(self.point) + '-' * (subwidth-num_length-1))
for n in range(subheight):
map.append(subquadmapsnorm['sw'][n] + '|' + subquadmapsnorm['se'][n])
return map
def search_pqtree(q, p, is_find_only=True):
if q is None:
return
if q.point == p:
if is_find_only:
return q
else:
return
dx,dy = 0,0
if p.x >= q.point.x:
dx = 1
if p.y >= q.point.y:
dy = 1
qnum = dx+dy*2
child = [q.quadrants['sw'], q.quadrants['se'], q.quadrants['nw'], q.quadrants['ne']][qnum]
if child is None and not is_find_only:
return q
return search_pqtree(child, p, is_find_only)
def insert_pqtree(q, p):
n = search_pqtree(q, p, False)
node = PQuadTreeNode(point=p)
if p.x < n.point.x and p.y < n.point.y:
n.quadrants['sw'] = node
elif p.x < n.point.x and p.y >= n.point.y:
n.quadrants['nw'] = node
elif p.x >= n.point.x and p.y < n.point.y:
n.quadrants['se'] = node
else:
n.quadrants['ne'] = node
def pointquadtree(data):
root = PQuadTreeNode(point = data[0])
for p in data[1:]:
insert_pqtree(root, p)
return root
#Test
data1 = [ (2,2), (0,5), (8,0), (9,8), (7,14), (13,12), (14,13) ]
points = [Point(d[0], d[1]) for d in data1]
q = pointquadtree(points)
print(q)
With your example data:
| | .|(14,13)
| |----(13,12)----
| ( 7,14)| .|.
|------------( 9, 8)------------
| .|.
| |
( 0, 5)| |
----------------------------( 2, 2)----------------------------
.|( 8, 0)
|
|
|
|
|
|
Tell me if you find it useful !

Python implementation of Determining DNA Health algorithm from HackerRank

I am trying to solve Determining DNA Health challenge from Hackerrank using python. (I have to add I am somewhat new to python 3. Still learning the language)
My solution fails for test cases 7, 8 and 9 with a message reading "Wrong Answer".
When I run the following code locally, I can confirm that for these test cases my implementation produces the expected output.
I am wondering what would be the problem.
I am a bit puzzled at the moment. Is there a problem with my implementation? If so how come it produces correct answers for 28 test cases but fails on these 3? Or is it a misleading/confusing result message from Hacker Rank, as I happen to know that people find these 3 test cases (7, 8 and 9) problematic from what I learnt from reading discussions.
Any help would be highly appreciated.
Here is the code I wrote:
from bisect import bisect_left
from bisect import bisect_right
import sys
from unittest.mock import right
class TrieNode(object):
def __init__(self):
self.subnodes = {}
self.isTerminal = False
self.indexList = []
self.healthList = []
def addSubnode(self, aChar):
if (self.subnodes.get(aChar)):
return self.subnodes[aChar]
else:
newNode = TrieNode()
self.subnodes[aChar] = newNode
return newNode
def addIndexAndValue(self, index, health):
self.isTerminal = True
self.indexList.append(index)
lastHealth = 0
healthLength = len(self.healthList)
if (healthLength>0):
lastHealth = self.healthList[healthLength-1]
self.healthList.append(lastHealth + health)
def getSubnodeFor(self, aChar):
return self.subnodes.get(aChar)
def getValueForIndexes(self, startIndex, endIndex):
listSize = len(self.indexList)
if listSize < 1:
return 0
elif listSize == 1:
if startIndex <= self.indexList[0] and endIndex >= self.indexList[0]:
return self.healthList[0]
else:
return 0
else: # listSize > 1
rightInd = bisect_left(self.indexList, endIndex)
if rightInd < listSize and endIndex < self.indexList[0]:
return 0
big = 0
if rightInd >= listSize:
big = self.healthList[listSize - 1]
else:
if endIndex >= self.indexList[rightInd]:
big = self.healthList[rightInd]
else:
big = self.healthList[rightInd-1]
leftInd = bisect_left(self.indexList, startIndex)
small = 0
if leftInd >= listSize:
return 0
else:
if startIndex <= self.indexList[leftInd]:
if (leftInd > 0):
small = self.healthList[leftInd - 1]
else:
small = 0
else:
small = self.healthList[leftInd]
return big - small
class Trie(object):
def __init__(self):
self.root = TrieNode()
def getRoot(self):
return self.root
def createTrie(self, genes, healths):
for i in range(len(genes)):
node = self.root
for c in genes[i]:
node = node.addSubnode(c)
node.addIndexAndValue(i, healths[i])
def calculateHealth(trie, d, first, last):
total = 0
dLength = len(d)
for i in range(0, dLength):
node = trie.getRoot()
for j in range(i, dLength):
node = node.getSubnodeFor(d[j])
if node != None:
if node.isTerminal:
val = node.getValueForIndexes(first, last)
total = total + val
else:
break
return total
def readFromFile(aFileName):
inputArr = None
with open('../hackerRank/src/' + aFileName, encoding='utf-8') as aFile:
inputArr = aFile.read().splitlines()
return inputArr
def runFor(fileName, minimumValue, maximumValue):
inp = readFromFile(fileName)
n = inp[0]
genes = inp[1].rstrip().split()
healths = list(map(int, inp[2].rstrip().split()))
trie = Trie()
trie.createTrie(genes, healths)
s = int(inp[3])
minVal = sys.maxsize
maxVal = -1
for fItr in range(s):
line = inp[fItr+4].split()
first = int(line[0])
last = int(line[1])
d = line[2]
val = calculateHealth(trie, d, first, last)
if val < minVal:
minVal = val
if val > maxVal:
maxVal = val
print (minVal,maxVal)
assert minimumValue == minVal
assert maximumValue == maxVal
# TextX.txt 's are simple text files, which hold test data for regarding test case
# following the file name are real expected numbers for each relevant test case
# I got those from hacker rank
runFor('Test2.txt', 15806635, 20688978289)
runFor('Test7.txt', 0, 7353994)
runFor('Test8.txt', 0, 8652768)
runFor('Test9.txt', 0, 9920592)
runFor('Test33.txt', 11674463, 11674463)
One reference that might assist can be found at:
https://gist.github.com/josephmisiti/940cee03c97f031188ba7eac74d03a4f
Please read the notes he has included.
This is the input I have been using.
6
a b c aa d b
1 2 3 4 5 6
3
1 5 caaab
0 4 xyz
2 4 bcdybc

Recursive Path finding error

I'm working on an exercise where given a set of connections between two points (ie. 12 is a connection between 1 and 2 ect.). I decided to tackle the approach recursively in order to have it systematically check every path and return when it finds one that hits every node and starts and ends with one.
However upon debugging this it seems that as I pass down the adjMatrix further into the recursion it's also editing the upper levels and causing it not to search any further as it goes back up the tree. I think it has something to when I set newMatrix = adjMatrix, but I'm not exactly sure.
def checkio(teleports_string):
#return any route from 1 to 1 over all points
firstnode, secondnode, size = 0, 0, 8
#Makes the adjacency matrix
adjMatrix = [[0 for i in range(size)] for j in range(size)]
for x in teleports_string:
#Assigns Variables
if firstnode == 0 and x != ",":
#print("Node1:" + x)
firstnode = x
elif secondnode == 0 and x != ",":
#print("Node2:" + x)
secondnode = x
#Marks connections
if firstnode != 0 and secondnode != 0:
adjMatrix[int(firstnode) - 1][int(secondnode) - 1] = 1
adjMatrix[int(secondnode) - 1][int(firstnode) - 1] = 1
firstnode, secondnode = 0, 0
print(adjMatrix)
return findPath(adjMatrix, 1, "1")
def findPath(adjMatrix, currentnode, currentpath):
if isFinished(currentpath):
return currentpath
for x in range(0, 8):
if adjMatrix[currentnode - 1][x] == 1:
print(currentpath + "+" + str(x+1))
newMatrix = adjMatrix
newMatrix[currentnode - 1][x] = 0
newMatrix[x][currentnode - 1] = 0
temp = currentpath
temp += str(x+1)
newpath = findPath(newMatrix, x+1,temp)
print(newpath)
if isFinished(newpath):
print ("Returning: " + newpath)
return newpath
return ""
def isFinished(currentpath):
#Checks if node 1 is hit at least twice and each other node is hit at least once
if currentpath == "":
return False
for i in range(1, 9):
if i == 1 and currentpath.count(str(i)) < 2:
return False
elif currentpath.count(str(i)) < 1:
return False
#Checks if it starts and ends with 1
if not currentpath.startswith(str(1)) or not currentpath.endswith(str(1)):
return False
return True
#This part is using only for self-testing
if __name__ == "__main__":
def check_solution(func, teleports_str):
route = func(teleports_str)
teleports_map = [tuple(sorted([int(x), int(y)])) for x, y in teleports_str.split(",")]
if route[0] != '1' or route[-1] != '1':
print("The path must start and end at 1")
return False
ch_route = route[0]
for i in range(len(route) - 1):
teleport = tuple(sorted([int(route[i]), int(route[i + 1])]))
if not teleport in teleports_map:
print("No way from {0} to {1}".format(route[i], route[i + 1]))
return False
teleports_map.remove(teleport)
ch_route += route[i + 1]
for s in range(1, 9):
if not str(s) in ch_route:
print("You forgot about {0}".format(s))
return False
return True
assert check_solution(checkio, "13,14,23,25,34,35,47,56,58,76,68"), "Fourth"
The line
newMatrix = adjMatrix
merely creates another reference to your list. You'll need to actually create a new list object. As this is a matrix, do so for the contents:
newMatrix = [row[:] for row in adjMatrix]
This creates a new list of copies of your nested lists.

Categories