Why does this pathfinding function crash? - python

I have an A* search algorithm which crashes the program because of a memory error, and I don't know why. These are the relevant bits of code:
def __init__(self, graph):
self.graph = graph
def search(self, start, end):
openset = set()
closedset = set()
current = start
openset.add(current)
while openset:
print current
current = min(openset, key=lambda o:o.g + o.h)
if current == end:
path = []
while current.parent:
path.append(current)
current = current.parent
path.append(current)
return path[::-1]
openset.remove(current)
closedset.add(current)
for node in self.graph[current]:
if node in closedset:
continue
if node in openset:
new_g = current.g + current.move_cost(node)
if node.g > new_g:
node.g = new_g
node.parent = current
else:
node.g = current.g + current.move_cost(node)
node.h = self.heuristic(node, start, end)
node.parent = current
openset.add(node)
return None
The graph passed to it is generated at the start of the program:
def make_graph(self, size, impassable):
nodes = [[astar_gridnode(x, y) for y in range(size)] for x in range(size)]
graph = {}
for x, y in product(range(size), range(size)):
node = nodes[x][y]
graph[node] = []
for i, j in product([-1, 0, 1], [-1, 0, 1]):
# Check that we are inside the grid area.
if not (0 <= x + i < size): continue
if not (0 <= y + j < size): continue
# Check if the target area is impassable.
if (x + i, y + j) in impassable: continue
# All looks good. Add target space as reachable from current (x, y) space.
graph[nodes[x][y]].append(nodes[x+i][y+j])
return graph, nodes
And here is how the search is called:
def find_path(self, agent, target_coords, impassable, graph, nodes):
paths = astar_grid(graph)
start = nodes[agent.grid_pos[0]][agent.grid_pos[1]]
end = nodes[target_coords[0]][target_coords[1]]
path = paths.search(start, end)
This all works like it's supposed to the first time a search is done, and it works if a search is done with start, end variables and a path which doesn't intersect the previous path. It also works if a new graph is generated before each search, but that's not possible because the graph object is huge and causes the program to freeze for a couple seconds when it's being created.
If a search is made which intersects with a previous path the program freezes for a minute, and I get this error:
File "C:\...\pathfinding.py", line 16, in find_path
path = paths.search(start, end)
File "C:\...\astar.py", line 19, in search
current = current.parent
MemoryError
What is the reason for the crash and how can we fix it? I don't understand why it would crash, as it seems to me that the original graph is not modified in a search, and that a new search object is created each time a search is called, which leaves me mystified as to why it works when it works, and crashes when it does.

I agree with hughdbrown — you very likely have a cycle in the parent chain, and printing out current immediately before you assign current = current.parent is likely to tell you whether this is true.
You say that the original graph is not modified in a search, but it is. You are modifying the .parent pointer. At first, all the .parent pointers are set to None, but after you've run the search, some of them are non-None. Since it should be None and it isn't, the while current.parent: condition is failing to find the end of the path, and it's branching off into previously computed paths.
Try setting start.parent = None at the beginning of the search. Or clear the parent pointers after the search finishes (more expensive but cleaner) (you only have to clear them for things in openset and closedset).

Related

8-Puzzle BFS output trouble in Python

This is an 8 puzzle and uses bfs and dfs to solve find the solution and prints out the path to the goal. I am having trouble poping and appending the children so that it can find the solution. My error is that it will only print out the two options and does not branch out from the possible solution. The terminal is still going despite not printing out anything.
Here is my code and on the bottom is a test case.
import copy
#This is the only file you need to work on. You do NOT need to modify other files
# Below are the functions you need to implement. For the first project, you only need to finish implementing bfs() and dfs()
#here you need to implement the Breadth First Search Method
def bfs(puzzle):
list = []
#initialization
state = copy.deepcopy(puzzle)
goal = [0,1,2,3,4,5,6,7,8]
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
#appending the first state
queue = []
queue = [Node(state)]
for node in queue[:]:
print('the state of this game position is:\n ' + str(node.state))
loop = True
notFound = True
l = 0
while loop:
for node in queue:
#blank index in each state
blank = node.state.index(8)
print('the index of the blank is '+ str(blank))
#The possible position
possible_pos = possible_move[blank]
print('possible pos '+ str(possible_pos))
if state != goal:
for i in possible_pos:
possible_sw = copy.deepcopy(node.state)
print('index swap = '+ str(i))
temp = possible_sw[i]
possible_sw[i] = 8
possible_sw[blank] = temp
print('the child nodes is ' + str(possible_sw))
node.insertChild(possible_sw)
if possible_sw == goal:
print('end')
notFound = False
loop = False
#check each child and find the goal state
for node in queue[:]:
for child_state in node.children:
if child_state == [0,1,2,3,4,5,6,7,8]:
final_state = child_state
print('the final state is '+ str(final_state.state))
queue.pop(0)
#find the parent path
while node.parent and loop is False:
sol_path = final_state.state
list.append(sol_path.index(8))
if final_state.parent is not None:
final_state = final_state.parent
else:
parent = False
list.reverse()
list.pop(0)
print('moves list '+ str(list))
return list
#here you need to implement the Depth First Search Method
def dfs(puzzle):
list = []
return list
#This will be for next project
def astar(puzzle):
list = []
return list
def swap(list, pos1, pos2):
list[pos1],list[pos2] = list[pos2], list[pos1]
return list
class Node:
def __init__(self,state,parent = None):
self.parent = parent
self.state = state
self.children = []
def insertChild(self, child_state):
self.children.append(Node(child_state,self))
#test cases
# p =[0, 1, 2, 3, 4, 5, 8, 6, 7]
p = [0, 1, 2, 3, 4, 5, 6, 8, 7]
#p = [0, 1, 2, 3, 8, 4, 6, 7, 5]
#p =[0, 4, 1, 3, 8, 2, 6, 7, 5]
bfs(p)
print("+++++++++++++++++++++")
#dfs(p)
There are several issues with your attempt:
The queue never receives more entries; queue.append is never called. On the other hand, the inner loop over queue[:] empties the queue with pop, removing its only element. And from that moment on the queue remains empty.
The Node constructor is called only once, so there will never be more than one node, and the test node.parent will always be false, making the last while loop useless, and the moveslist (if any) will never be printed
If the end is not found in the first iteration -- meaning the initial position is not one move away from the goal -- the outer loop will get into an infinite loop: on its second iteration the queue is empty, so there is nothing to do, and the loop name will never become True.
if state != goal makes little sense as the state name never changes in the loop. If anything, this should reference node.state, not state.
The list.pop(0) at the very end unnecessarily removes a move. The loop condition already checks if the node has a parent -- so skipping the root state -- so then you'll miss two states.
The code does not check whether the initial position is maybe the goal position, and so it will not return an empty move list as solution when this is the case.
Some other remarks:
swap is never called.
The code has several names which serve no purpose, like l, notFound.
It uses list which is a native name in Python -- choose a different name.
The children attribute of the Node instances is not useful. Although you iterate it to find the final state, the logic for identifying whether the goal was reached, is already present elsewhere in the code... it doesn't need children.
deepcopy is not needed: the lists you use are not "deep". You can simply copy them by applying the list (native) function, or slicing them with [:].
Assigning twice to queue in sequence (in the initialisation) makes the first assignment useless. Just have the second assignment.
The loop in the initialisation part should not be a loop. At that moment you know the queue has only one entry, so iterating it is overkill. You can just output the initial state using puzzle. But maybe you wanted to output the state in the loop...
There is no need for temp to perform a swap. First of all, Python can do tuple assignment, but also: a move is not really a swap of two unknown values: you know that one of the two is the empty cell (8), so you can safely overwrite that cell with the other cell's value, and then set the other cell's value to 8 -- no temp is needed.
Here is your code corrected with the above remarks:
class Node:
def __init__(self,state,parent = None):
self.parent = parent
self.state = state
def bfs(puzzle):
solution = []
#initialization
goal = [0,1,2,3,4,5,6,7,8]
possible_move = [[1,3],[0,2,4],[1,5],[0,4,6],[1,3,5,7],[2,4,8],[3,7],[4,6,8],[5,7]]
node = Node(puzzle)
queue = [node]
while True:
node = queue.pop(0)
print('the state of this game position is:\n ' + str(node.state))
if node.state == goal:
break
blank = node.state.index(8)
print('the index of the blank is '+ str(blank))
possible_pos = possible_move[blank]
print('possible pos '+ str(possible_pos))
for i in possible_pos:
possible_sw = node.state[:]
print('index swap = '+ str(i))
possible_sw[blank] = possible_sw[i]
possible_sw[i] = 8
print('the child node is ' + str(possible_sw))
queue.append(Node(possible_sw, node))
while node.parent:
solution.append(node.state.index(8))
node = node.parent
solution.reverse()
print('moves list '+ str(solution))
return solution

Given a binary tree and a number ‘S’, find all paths from root-to-leaf such that the sum of all the node values of each path equals ‘S’

The example below is from a source online and what I'm not sure of is why we need to append to allPaths a new copy of currentPath. I thought that in deleting the last element as we go back up the recursive call stack by doing del currentPath[-1] makes sure that we are not adding the previous path to the new path
class TreeNode:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def find_paths(root, required_sum):
allPaths = []
find_paths_recursive(root, required_sum, [], allPaths)
return allPaths
def find_paths_recursive(currentNode, required_sum, currentPath, allPaths):
if currentNode is None:
return
# add the current node to the path
currentPath.append(currentNode.val)
# if the current node is a leaf and its value is equal to required_sum, save the current path
if currentNode.val == required_sum and currentNode.left is None and currentNode.right is None:
allPaths.append(list(currentPath))
else:
# traverse the left sub-tree
find_paths_recursive(currentNode.left, required_sum -
currentNode.val, currentPath, allPaths)
# traverse the right sub-tree
find_paths_recursive(currentNode.right, required_sum -
currentNode.val, currentPath, allPaths)
# remove the current node from the path to backtrack,
# we need to remove the current node while we are going up the recursive call stack.
del currentPath[-1]
def main():
root = TreeNode(12)
root.left = TreeNode(7)
root.right = TreeNode(1)
root.left.left = TreeNode(4)
root.right.left = TreeNode(10)
root.right.right = TreeNode(5)
required_sum = 23
print("Tree paths with required_sum " + str(required_sum) +
": " + str(find_paths(root, required_sum)))
main()
It is important to realise that during the whole process there is only one currentPath list. It is the one that is created in the initial call:
find_paths_recursive(root, required_sum, [], allPaths)
# ^^---here!
All that happens to that single list is that elements are appended to it, and then deleted from it again (when backtracking). It continually grows and shrinks, grows and shrinks,... throughout its lifetime. But it is the same, single list instance.
If you were to append that list to allPaths without taking a copy, i.e. like this:
allPaths.append(currentPath)
...then realise that while that list is a member of allPaths, it will be mutated by future append and delete actions on it! Even more: as the above statement is executed again later on:
allPaths.append(currentPath)
... exactly the same list is appended that is already in allPaths... because there is only one currentPath list! So you'll end up with allPaths having repeated references to one and the same list.
Concluding: it is important to take a copy of currentPath which will not be mutated any more by the future mutations on currentPath. It is like taking a snapshot of the current state of currentPath.
The find_paths_recursive function was designed such that appending to allPaths is the way to return the results to the caller.
def find_paths(root, required_sum):
allPaths = []
find_paths_recursive(root, required_sum, [], allPaths)
return allPaths
Here in find_paths, allPaths is passed to find_paths_recursive as an empty list and after it is done, it will contain the results (paths from root to leaf which fulfill the described condition).
I would recommend breaking the problem down into separate parts. First we write a generic paths function -
def paths (t = None, p = ()):
if not t:
return
elif t.left or t.right:
yield from paths(t.left, (*p, t.val))
yield from paths(t.right, (*p, t.val))
else:
yield (*p, t.val)
mytree = TreeNode \
( 12
, TreeNode(7, TreeNode(4))
, TreeNode(1, TreeNode(10))
)
Now we can see how paths works -
for p in paths(mytree):
print(p)
(12, 7, 4)
(12, 1, 10)
Now we can write solver that specializes paths -
def solver (t = None, q = 0):
for p in paths(t):
if sum(p) == q:
yield p
solver is a generator which yields all possible solutions. It is a good choice for programs like this because you can pause/cancel the solving as soon as you found the solution(s) you are looking for -
for sln in solver(mytree, 23):
print(sln)
The output isn't particularly interesting because each branch in mytree sums to 23 -
(12, 7, 4)
(12, 1, 10)
If we make anothertree with different values, we can see more interesting output -
anothertree = TreeNode \
( 1
, TreeNode(7, TreeNode(4), TreeNode(5))
, TreeNode(9, TreeNode(2), TreeNode(7))
)
for sln in solver(anothertree, 12):
print(sln)
(1, 7, 4)
(1, 9, 2)

Why the path variable cannot be returned?

I am using DFS to get all routes between two nodes.
My python code is as follows:
graph = {0: [1, 2, 3],
1: [3],
2: [0, 1],
3: []}
def DFS(start, stop, path=[], visited=[]):
global count
global result
# add the visited node to path
path.append(start)
# mark this node visited to avoid infinite loop
visited.append(start)
# found
if start == stop:
print(path)
else:
# if not found
values = graph.get(start)
for next_ in values:
# not visited node
if not next_ in visited:
DFS(next_, stop, path, visited)
# remove the node from path and unmarked it
path.remove(start)
visited.remove(start)
The problem is that if I print path in if start == stop, all 3 routes can be printed correctly.
>>> DFS(2, 3)
[2, 0, 1, 3]
[2, 0, 3]
[2, 1, 3]
But if I change to return path in if start == stop, it would return nothing.
def DFS(start, stop, path=[], visited=[]):
global count
global result
# add the visited node to path
path.append(start)
# mark this node visited to avoid infinite loop
visited.append(start)
# found
if start == stop:
return path
else:
# if not found
values = graph.get(start)
for next_ in values:
# not visited node
if not next_ in visited:
DFS(next_, stop, path, visited)
# remove the node from path and unmarked it
path.remove(start)
visited.remove(start)
>>> result = DFS(2, 3)
>>> result
But if I change to return path in if start == stop, it would return nothing.
Right; because you got to this level of recursion from the previous one, which recursively called DFS(next_, stop, path, visited)... and ignored the result.
It is the same as if you called functions normally:
def inner():
return "hello"
def outer():
inner() # oops, it is not returned.
print(outer()) # None
In general you want to return the results from your recursive calls; but your case is a little special because you need to accumulate the results from multiple recursive calls (for next_ in values:). You could build a list and return it, but this is a bit tricky:
if start == stop:
result = [path] # for uniformity, we need a list of paths in this case too.
# Also, we can't `return` here, because we'll miss the cleanup at the end.
else:
result = []
values = graph.get(start)
for next_ in values:
# BTW, Python treats `not in` as a single operator that does
# what we want here. It's preferred because it's easier to read.
if next_ not in visited:
# add results from the recursive call to our result.
result.extend(DFS(next_, stop, path, visited))
# it is `.extend` and not `.append` here because otherwise we will
# build a tree of nested lists - do you understand why?
# Either way, we want to do our cleanup, and return the collected result.
path.remove(start)
visited.remove(start)
return result # important!
Tricky, right?
My preferred solution for these situations, therefore, is to write a recursive generator, and collect the results outside the recursion:
# Inside the function, we do:
if start == stop:
yield path
else:
values = graph.get(start)
for next_ in values:
if next_ not in visited:
yield from DFS(next_, stop, path, visited))
path.remove(start)
visited.remove(start)
# Then when we call the function, collect the results:
paths = list(DFS(2, 3))
# Or iterate over them directly:
for path in DFS(2, 3):
print("For example, you could take this route:", path)
(Also, the comment you received was good advice. Recursion is a lot easier to understand when you don't try to mutate the arguments and clean up afterwards. Instead, always pass those arguments, and when you make the recursive call, pass a modified version. When the recursion returns, cleanup is automatic, because you just go back to using the old object in the old stack frame.
The problem with your code is that
result=DFS(3,2)
will only return a valid result if start=stop which is not the case as 3!=2.
To get the desired output you have to change the line
DFS(next_,stop,path,visited)
to
return DFS(next_,stop,path,visited)
Now whenever start gets equal to stop the path will be returned and this value will be propogated upwards

Pathfinding code produces unexpected results

First of all, excuse the bad title, but I don't know how to describe this in just one sentence...
Given a grid with 3 kinds of fields, empty fields, walls and exits, I wrote a program that checks for every empty field, whether that field is "safe".
A person walks through that grid, but can only walk non-diagonally and can't go through walls. The person, starting at one field, chooses one direction at random and starts walking that way. Once it hits a wall, it chooses a direction at random again, starting to move into that direction and so on.
A field is considered safe if a person traversing the grid as described above, starting at that field, is guaranteed to find an exit at some point.
I wrote a Python program to solve this problem. It builds a "tree" for every field it checks, containing every possible route from that field.
I have a function that just returns the "parent" of a given node, by recursively adding the parent of the current node to a list of nodes until it reaches the topmost node.
The program works as expected when checking only one field, for example (1, 4). However it doesn't work when checking all fields of the example grid.
I already looked into it and realized that the alle_parents() function which returns all parents of a given node yields unexpected results when checking all nodes. E.g. when checking the field (1, 4), one child of that node is (1, 8). The parents of (1, 8) should just be (1, 4). That's not the case, though. alle_parents((1, 8)) returns many different fields that shouldn't be there. However I can't figure out why it behaves as it does. My only guess is that it has to do with "left-over" data/GC not working as intended.
Relevant code:
class Knoten():
def __init__(self, x, y, parent = None):
self.x = x
self.y = y
self.parent = parent
self.children = []
n = len(spielfeld)
m = len(spielfeld[0])
for k in range(n):
for j in range(m):
if spielfeld[k][j] not in [None, '#', 'E']:
baum = []
i = 0
ebene = []
ebene.append(Knoten(k, j))
baum.append(ebene)
i += 1
while i <= 100:
ebene = []
for knoten in baum[i - 1]:
children = []
if spielfeld[knoten.x][knoten.y] == 'E':
continue
for feld in next_feld(knoten.x, knoten.y):
knoten_neu = Knoten(feld[0], feld[1], knoten)
hinzufuegen = True
for parent in alle_parents(knoten_neu):
if knoten_neu.x == parent.x and knoten_neu.y == parent.y:
hinzufuegen = False
if hinzufuegen:
ebene.append(knoten_neu)
children.append(knoten_neu)
knoten.children = children
if children == []:
if spielfeld[knoten.x][knoten.y] != 'E':
spielfeld[k][j] = '%' # Field not safe
baum.append(ebene)
i += 1
def alle_parents(knoten, parents = []):
if knoten.parent == None:
return parents
else:
parents.append(knoten.parent)
return alle_parents(knoten.parent, parents)
The example map I'm using:
############
# # # #
# ## #
# # E# #
# ## #
# #
# #E E###
############
Full code (parts of it are German, sorry for that): http://pastebin.com/3XUBbpkK
I suspect your issue is a common Python gotcha. This line:
def alle_parents(knoten, parents = []):
Creates an empty array when the module is loaded, NOT every time the function is called. Future calls to alle_parents() will reuse the same array (which may have grown in size) instead of a new empty array! A good way to fix is to do this:
def alle_parents(knoten, parents = None):
parents = parents or []

Python recursion - how to exit early

I've been playing with BST (binary search tree) and I'm wondering how to do an early exit. Following is the code I've written to find kth smallest. It recursively calls the child node's find_smallest_at_k, stack is just a list passed into the function to add all the elements in inorder. Currently this solution walks all the nodes inorder and then I have to select the kth item from "stack" outside this function.
def find_smallest_at_k(self, k, stack, i):
if self is None:
return i
if (self.left is not None):
i = self.left.find_smallest_at_k(k, stack, i)
print(stack, i)
stack.insert(i, self.data)
i += 1
if i == k:
print(stack[k - 1])
print "Returning"
if (self.right is not None):
i = self.right.find_smallest_at_k(k, stack, i)
return i
It's called like this,
our_stack = []
self.root.find_smallest_at_k(k, our_stack, 0)
return our_stack[k-1]
I'm not sure if it's possible to exit early from that function. If my k is say 1, I don't really have to walk all the nodes then find the first element. It also doesn't feel right to pass list from outside function - feels like passing pointers to a function in C. Could anyone suggest better alternatives than what I've done so far?
Passing list as arguments: Passing the list as argument can be good practice, if you make your function tail-recursive. Otherwise it's pointless. With BST where there are two potential recursive function calls to be done, it's a bit of a tall ask.
Else you can just return the list. I don't see the necessity of variable i. Anyway if you absolutely need to return multiples values, you can always use tuples like this return i, stack and this i, stack = root.find_smallest_at_k(k).
Fast-forwarding: For the fast-forwarding, note the right nodes of a BST parent node are always bigger than the parent. Thus if you descend the tree always on the right children, you'll end up with a growing sequence of values. Thus the first k values of that sequence are necessarily the smallest, so it's pointless to go right k times or more in a sequence.
Even in the middle of you descend you go left at times, it's pointless to go more than k times on the right. The BST properties ensures that if you go right, ALL subsequent numbers below in the hierarchy will be greater than the parent. Thus going right k times or more is useless.
Code: Here is a pseudo-python code quickly made. It's not tested.
def findKSmallest( self, k, rightSteps=0 ):
if rightSteps >= k: #We went right more than k times
return []
leftSmallest = self.left.findKSmallest( k, rightSteps ) if self.left != None else []
rightSmallest = self.right.findKSmallest( k, rightSteps + 1 ) if self.right != None else []
mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
return mySmallest[:k]
EDIT The other version, following my comment.
def findKSmallest( self, k ):
if k == 0:
return []
leftSmallest = self.left.findKSmallest( k ) if self.left != None else []
rightSmallest = self.right.findKSmallest( k - 1 ) if self.right != None else []
mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
return mySmallest[:k]
Note that if k==1, this is indeed the search of the smallest element. Any move to the right, will immediately returns [], which contributes to nothing.
As said Lærne, you have to care about turning your function into a tail-recursive one; then you may be interested by using a continuation-passing style. Thus your function could be able to call either itself or the "escape" function. I wrote a module called tco for optimizing tail-calls; see https://github.com/baruchel/tco
Hope it can help.
Here is another approach: it doesn't exit recursion early, instead it prevents additional function calls if not needed, which is essentially what you're trying to achieve.
class Node:
def __init__(self, v):
self.v = v
self.left = None
self.right = None
def find_smallest_at_k(root, k):
res = [None]
count = [k]
def helper(root):
if root is None:
return
helper(root.left)
count[0] -= 1
if count[0] == 0:
print("found it!")
res[0] = root
return
if count[0] > 0:
print("visiting right")
find(root.right)
helper(root)
return res[0].v
If you want to exit as soon as earlier possible, then use exit(0).
This will make your task easy!

Categories