I was trying to do a depth first recursive search in python on a graph class I made. I failed the test at the assertion level for some unknown reason: AssertionError: None not found in [[1, 2, 4, 6], [1, 2, 4, 7, 6]]
, I understand the meaning of that error and I wasn't going to ask about the algorithm directly but about something else I noticed.
Using VS Code python debugger, I decided to inspect my code in operation and I notice as new stackframes got pushed to the stack with each new call (see image), they shared the same values for the 'visited' (set) and 'path' (list) instances (completely unexpected and confusing). I added a callnumber dummy variable as a sanity check to ensure I was using the debugger correctly, and I was.
I don't know why I didn't catch it earlier, especially since I've been doing a lot with C and memory management. These objects are obviously being put on the heap and passed by reference, and therefore not really separate instances, right?
Is there any way to make these stack based variables? I know that's not recommended as there's a risk of stack overflow (no pun intended) and not a good use of space.
Now that I understand what's going on, I was wondering if you had some pointers (pun intended this time) on how I make this algorithm go, given that this approach doesn't work because it's heap based? I don't need it solved, just some tips or clues, stuff that generates deeper thought.
Btw I'm always trying to get better at reading and understanding documentation. I like the docs to be my first go to actually, but when I google something for python, they're always close to the bottom past random tutorials unlike for other languages. I went to the official page on built in types just now and searched for words like heap or memory for list and dict and nowhere did I find it explained where these objects live, how they grow or are resized, etc, although it's probably so obvious and implicit for experienced programmers but anyway here is my code
class Graph:
def __init__(self):
self.vertices = {}
def add_vertex(self, vertex_id):
self.vertices[vertex_id] = set()
def add_edge(self, v1, v2):
self.vertices[v1].add(v2)
# more functions
def dfs_recursive(self, starting_vertex, destination_vertex, visited = set(), path=[]):
"""
Return a list containing a path from
starting_vertex to destination_vertex in
depth-first order.
This should be done using recursion.
"""
result = None
path.append(starting_vertex)
visited.add(starting_vertex)
if starting_vertex == destination_vertex:
return path
for neighbor in self.vertices[starting_vertex]:
if neighbor not in visited:
visited.add(neighbor)
result = self.dfs_recursive(neighbor, destination_vertex, visited, path, callnumber+1)
return result
Edit: Solved the algorithm thanks to input of rioV8. Here is my code
def dfs_recursive(self, starting_vertex, destination_vertex, visited = None, path=None, callnumber=1):
path = path or []
visited = visited or set()
path.append(starting_vertex)
visited.add(starting_vertex)
if starting_vertex == destination_vertex:
return path
for neighbor in self.vertices[starting_vertex]:
if neighbor not in visited:
result = self.dfs_recursive(neighbor, destination_vertex, visited.copy(), path[:], callnumber+1)
if result is not None and result[-1] == destination_vertex:
return result
return None
Don't use modifiable objects as function default arguments.
They are created at function compilation and not at function call. And are reused for every call.
Use None
and create the modifiable inside the function.
And make a copy of the modifiable at the recursive call.
def dfs_recursive(self, starting_vertex, destination_vertex, visited=None, path=None):
visited = visited or set()
path = path or []
# rest of function
result = self.dfs_recursive(neighbor, destination_vertex, visited.copy(), path[:], callnumber+1)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.