Мне сложно отслеживать фрагменты, созданные getAdjacentTiles(..)
. Я определил, что проблема производительности моей реализации A * ниже заключается в том, что я не отслеживаю фрагменты, которые видели раньше, каждый вызов getAdjacentTiles
возвращает новые фрагменты (Node
), а не любые фрагменты в openSet
или closedSet
. Я решил использовать список объектов Node, как и все созданные до сих пор плитки, и передать это getAdjacentTiles
чтобы определить, была ли уже getAdjacentTiles
плитка.
Моя проблема в том, что я не могу правильно отслеживать эти плитки. Всякий раз, когда мой A * должен принимать более 4 движений, чтобы добраться до end
местоположения, он дергается. Который я уверен, имеет отношение к тому, как я пытаюсь отслеживать плитки (снова Node
который был посещен). Я должен был бы заподозрить, что проблема связана с моими знаниями о python, мне разрешено делать .apped(tile)
как я делаю в getAdjacentTiles(...)
при allTiles
через набор allTiles
?
Вот ссылка на вопрос, который привел меня к этому
Ошибка генерируется (иногда, только когда путь A * длиннее 3 шагов).
File "P3.py", line 67, in aStar
openSet.remove(curNode)
KeyError: <__main__.Node instance at 0xa39edcc>
Источник
#Perform an A* search to find the best path to the dirt
def aStar(self, current, end):
openSet = set()
openHeap = []
closedSet = set()
allTiles = set()
curNode = Node(0, current, self.manHatDist(current, end))
openSet.add(curNode)
allTiles.add(curNode)
openHeap.append((curNode.cost,curNode))
while openSet:
curNode = heapq.heappop(openHeap)[1]
if curNode.pos == end:
return self.getDirections(curNode)
openSet.remove(curNode)
closedSet.add(curNode)
adjNodes = self.getAdjacentNodes(curNode.pos, allTiles)
for tile in adjNodes:
t = tile
if t not in closedSet:
cost = (curNode.cost - self.manHatDist(curNode.pos, end)
+ self.euclidDist(curNode.pos, current)
+ self.manHatDist(t.pos, end))
if t not in openSet or cost < t.cost:
t.parent = curNode
t.cost = cost
openSet.add(t)
heapq.heappush(openHeap, (cost,t))
allTiles.add(t)
return []
#Get the moves made to get to this endNode
def getDirections(self, endNode):
moves = []
tmpNode = endNode
while tmpNode.parent is not None:
moves.append(tmpNode.value)
tmpNode = tmpNode.parent
moves.reverse()
return moves
#Return all possible moves from given tile as Node objects
def getAdjacentNodes(self, curPos, allTiles):
allMoves = ['North','South','East','West']
posMoves = []
for direction in allMoves:
if(self.canMove(direction, curPos)):
posMoves.append(Node(direction, self.getLocIfMove(curPos, direction)))
retNodes = []
for posLocNode in posMoves:
set = False
for tile in allTiles:
if(posLocNode.pos == tile.pos):
set = True
retNodes.append(tile)
if(not set):
retNodes.append(posLocNode)
return retNodes
Позвольте запустить интерактивный интерпретатор и посмотреть, что мы можем найти. (Вы не указали название своего класса в вопросе, поэтому я назвал его " Search
).
>>> Search().aStar((0,0), (2,2))
Traceback (most recent call last):
...
File "q19128695.py", line 25, in aStar
openSet.remove(curNode)
KeyError: <__main__.Node instance at 0x104895518>
ОК, первая проблема заключается в том, что эти экземпляры Node
не являются самоочевидными. Мы ничего не можем сделать с "экземпляром Node в 0x104895518", поэтому добавьте метод __repr__
в класс Node
:
def __repr__(self):
return 'Node({0.value}, {0.pos}, {0.cost})'.format(self)
и попробуй еще раз:
>>> Search().aStar((0,0), (2,2))
Traceback (most recent call last):
...
File "q19128695.py", line 25, in aStar
openSet.remove(curNode)
KeyError: Node(East, (1, 2), 3.41421356237)
Хорошо, это более информативно. Позвольте запустить отладчик Python и сделать посмертное:
>>> import pdb
>>> pdb.pm()
> q19128695.py(25)aStar()
-> openSet.remove(curNode)
(Pdb) openSet
set([Node(North, (2, -1), 6.0), Node(East, (2, 2), 4.65028153987),
Node(West, (-1, 1), 5.0), Node(North, (0, -1), 5.0),
Node(South, (1, 3), 6.65028153987), Node(South, (0, 3), 6.0),
Node(East, (3, 0), 6.0), Node(West, (-1, 0), 5.0),
Node(North, (1, -1), 5.0), Node(East, (3, 1), 6.65028153987),
Node(West, (-1, 2), 6.0)])
(Pdb) closedSet
set([Node(0, (0, 0), 4), Node(South, (2, 1), 3.41421356237),
Node(East, (1, 1), 3.0), Node(South, (0, 1), 3.0),
Node(East, (2, 0), 3.0), Node(East, (1, 0), 3.0),
Node(East, (1, 2), 3.41421356237), Node(South, (0, 2), 3.0)])
(Pdb) curNode
Node(East, (1, 2), 3.41421356237)
(Pdb) curNode in closedSet
True
Итак, узел уже закрыт. Как это могло произойти? Ну, это может произойти, если узел был добавлен в openSet
и openHeap
дважды. Затем он будет openHeap
из openHeap
дважды (потому что кучи могут иметь несколько одинаковых элементов), но его можно удалить openSet
один раз из openSet
. Этот код выглядит следующим образом:
if t not in openSet or cost < t.cost:
t.parent = curNode
t.cost = cost
openSet.add(t)
heapq.heappush(openHeap, (cost,t))
Первая проблема заключается в том, что вы нажимаете пару (cost, t)
даже если у вас возникли проблемы с предоставлением объектов Node
__lt__
и __gt__
. Вместо этого просто нажмите t
на кучу:
heapq.heappush(openHeap, t)
Для этого требуется несколько изменений в другом месте: вместо
openHeap.append((curNode.cost,curNode))
while openSet:
curNode = heapq.heappop(openHeap)[1]
вам придется писать
openHeap = [curNode]
while openSet:
curNode = heapq.heappop(openHeap)
Теперь вторая проблема (это моя ошибка - извините) заключается в том, что если t
уже находится в openSet
мы не должны снова добавлять его в кучу. Вместо этого мы должны повторно использовать:
t_open = t in openSet
if not t_open or cost < t.cost:
t.parent = curNode
t.cost = cost
if t_open:
heapq.heapify(openHeap)
else:
openSet.add(t)
heapq.heappush(openHeap, t)
Возвращаясь к выводу отладчика, напомните следующее:
(Pdb) curNode
Node(East, (1, 2), 3.41421356237)
То, что 3.41421356237
должно беспокоить вас: не стоит ли всегда стоить целое число? Похоже, расчет стоимости по-прежнему не так. В нем говорится:
cost = (curNode.cost
- self.manHatDist(curNode.pos, end)
+ self.euclidDist(curNode.pos, current)
+ self.manHatDist(t.pos, end))
но эта третья строка должна сказать:
+ self.euclidDist(curNode.pos, t.pos)
Итак, со всеми исправлениями, попробуйте еще раз:
>>> Search().aStar((0,0), (2,2))
['North', 'North', 'East', 'East']
"Как вы назвали Search().aStar(...)
от переводчика?" Я запустил интерпретатор и набрал эту строку кода в командной строке интерпретатора. См. Учебник.
"Таким образом, евклидово расстояние всегда будет одним". Да, если вы ищете пути в сетке с равномерной стоимостью, то евклидово расстояние между соседями всегда будет одинаковым.
"Теперь, когда я думаю об этом, curNode.cost - self.manHatDist(curNode.pos, end)
всегда равен нулю". Это не так. В вашей реализации cost
поискового узла: (i) стоимость достижения этого узла с самого начала, плюс (ii) допустимая оценка стоимости достижения конца от этого узла. Поэтому, если вы вычтите допустимую оценку, вы должны вернуться к (i) снова.
Search().aStar(..)
от переводчика? // Вы только что научили меня отладке в python, спасибо. // Теперь я вижу, что вы имели в виду в связанном посте в моем вопросе выше, self.euclidDist(curNode.pos, t.pos)
всегда будет равно единице, t всегда и только узел либо NSEW из curNode
; так что евклидово расстояние всегда будет одним. // Теперь я думаю, что думаю об этом curNode.cost - self.manHatDist(curNode.pos, end)
всегда равен нулю. Я думаю, что я был сбит с толку эвристикой и допустимостью. //
openSet
на каждом шаге?manHatDist
есть заглавнаяmanHatDist
H?