BFS algorithm in Python - python

graph={ 0:[1,3,4], 1:[0,2,4], 2:[1,6], 3:[0,4,6], 4:[0,1,3,5], 5:[4], 6:[2,3] }
def bfs(graph, start, path=[]):
queue = [start]
while queue:
vertex = queue.pop(0)
if vertex not in path:
path.append(vertex)
queue.extend(graph[vertex] - path)
return path
print bfs(graph, 0)
Guys! Can someone help me with this bfs code? I can't understand how to solve this queue line.

To extend your queue with all nodes not yet seen on the path, use set operations:
queue.extend(set(graph[vertex]).difference(path))
or use a generator expression:
queue.extend(node for node in graph[vertex] if node not in path)
Lists don't support subtraction.
You don't really need to filter the nodes, however, your code would work with a simple:
queue.extend(graph[vertex])
as the if vertex not in path: test also guards against re-visiting nodes.
You should not use a list as default argument, see "Least Astonishment" and the Mutable Default Argument; you don't need a default argument here at all:
def bfs(graph, start):
path = []
Demo:
>>> graph={ 0:[1,3,4], 1:[0,2,4], 2:[1,6], 3:[0,4,6], 4:[0,1,3,5], 5:[4], 6:[2,3] }
>>> def bfs(graph, start):
... path = []
... queue = [start]
... while queue:
... vertex = queue.pop(0)
... if vertex not in path:
... path.append(vertex)
... queue.extend(graph[vertex])
... return path
...
>>> print bfs(graph, 0)
[0, 1, 3, 4, 2, 6, 5]

queue.extend(graph[vertex] - path)
This line is giving TypeError: unsupported operand type(s) for -: 'list' and 'list', because you are not allowed to subtract two lists. You could convert them to a different collection that does support differences. For example:
graph={ 0:[1,3,4], 1:[0,2,4], 2:[1,6], 3:[0,4,6], 4:[0,1,3,5], 5:[4], 6:[2,3] }
def bfs(graph, start, path=[]):
queue = [start]
while queue:
vertex = queue.pop(0)
if vertex not in path:
path.append(vertex)
queue.extend(set(graph[vertex]) - set(path))
return path
print bfs(graph, 0)
Result:
[0, 1, 3, 4, 2, 6, 5]
By the way, it may be good to modify the argument list so that you don't have a mutable list as a default:
def bfs(graph, start, path=None):
if path == None: path = []

Bug is that there is no list difference method. Either you can convert it to set and use set difference method or you can use list comprehension as
queue.extend(graph[vertex] - path)
can be replaced by
queue += [i for i in graph[vertex] if i not in path].

#USE BELOW CODE FOR SIMPLE UNDERSTANDING
graph = {
'A' : ['B' , 'C','D'],
'B' : ['E'],
'C' : ['F'],
'D' : ['G'],
'E' : [],
'F' : ['Z'],
'G' : [],
'Z' : [],
}
visited = [] #Store visted nodes
queue = [] #BFS uses queue structure so this varible will work like QUEUE ( LIFO)
final_result = []
def bfs(visited,graph,node):
visited.append(node)
queue.append(node)
while queue:
s = queue.pop(0)
print(s,end=" ")
#final_result.append(s)
for neighbour in graph[s]:
if neighbour not in visited:
visited.append(neighbour)
queue.append(neighbour)
bfs(visited,graph,'A')
print(final_result)

Related

Pointer error in Directed Graph in Python

I'm trying to implement a Directed graph in python by creating a class Vertex. A vertex has the name and the adjacent as a list containing another instance of class Vertex . below is the code.
The problem is that when I assign node b as the adjacent node from node a. the pointer of node b is assigned to both adjacent of a and b which is not my intention.
I also have the picture debug mode below the code showing that the pointer of node b is also assigned to itself.
how to fix it?
class Vertex:
def __init__(self, name, adjacent=[]):
self.name = name
self.adjacent = adjacent
def add_adjacent(self, vertex):
self.adjacent.append(vertex)
class Graph:
# directed graph
def __init__(self, edge_list):
vertices = {}
for o, d in edge_list:
# print(o, d)
if o not in vertices:
v = Vertex(o)
vertices[o] = v
else:
v = vertices[o]
if d not in vertices:
u = Vertex(d)
vertices[d] = u
else:
u = vertices[d]
if u not in v.adjacent:
print(v.name, ' adds ', u.name)
v.add_adjacent(u)
self.vertices = vertices
def get_vertex_names(self):
return list(self.vertices.keys())
def get_adjacent(self, vertex):
return self.vertices[vertex].adjacent
# test Vertex
edges = [
['a', 'b'],
['a', 'c'],
['a', 'd'],
['b', 'c'],
['c', 'b'],
]
g = Graph(edges)
# g.vertices
error can be seen in debug mode
You wrote this:
def __init__(self, name, adjacent=[]):
There is a subtle "gotcha",
summarized by a simple rule,
Avoid using mutable container as a default arg.
You want this:
def __init__(self, name, adjacent=None):
self.name = name
self.adjacent = adjacent or []
In the original, there is a single list
for all vertices (since caller always lets it default).
That list was created at def time.
In contrast, the or [] clause
constructs a new list at execution time
each time through, rather than once during definition.
https://dollardhingra.com/blog/python-mutable-default-arguments/
https://towardsdatascience.com/python-pitfall-mutable-default-arguments-9385e8265422

Topological Sort Algorithm (DFS) Implementation in Python

I am new to python and algorithms. I have been trying to implement a topological sorting algorithm for a while but can't seem to create a structure that works. The functions I have made run on a graph represented in an adj list.
When I have a DFS, the nodes are discovered top down, and nodes that have been already visited and not processed again:
def DFS(location, graph, visited = None):
if visited == None:
visited = [False for i in range(len(graph))]
if visited[location] == True:
return
visited[location] = True
node_visited.append(location)
for node in graph[location]:
DFS(node, graph, visited)
return visited
When I am trying to build a topological sort algorithm, I create a new function which essentially checks the "availability" of that node to be added to the sorted list (ie: whether its neighbouring nodes have been visited already)
def availability(graph, node):
count = 0
for neighbour in graph[node]:
if neighbour in available_nodes:
count += 1
if count != 0:
return False
return True
However, my issue is that once I have visited the node path to get to the bottom of the graph, the DFS does not allow me to revisit that those nodes. Hence, any updates I make once I discover the end of the path can not be processed.
My approach may be totally off, but I am wondering if someone could help improve my implementation design, or explain how the implementation is commonly done. Thanks in advance.
You don't need that availability check to do a topological sort with DFS.
DFS itself ensures that you don't leave a node until its children have already been processed, so if you add each node to a list when DFS finishes with it, they will be added in (reverse) topological order.
Don't forget to do the whole graph, though, like this:
def toposort(graph):
visited = [False for i in range(len(graph))]
result = []
def DFS(node):
if visited[node]:
return
visited[node] = True
for adj in graph[node]:
DFS(adj)
result.append(node)
for i in range(len(graph)):
DFS(i)
return result
class Graph:
def __init__(self):
self.edges = {}
def addNode(self, node):
self.edges[node] = []
def addEdge(self, node1, node2):
self.edges[node1] += [node2]
def getSub(self, node):
return self.edges[node]
def DFSrecu(self, start, path):
for node in self.getSub(start):
if node not in path:
path = self.DFSrecu(node, path)
if start not in path:
path += [start]
return path
def topological_sort(self, start):
topo_ordering_list = self.DFSrecu(start, [])
# this for loop it will help you to visit all nodes in the graph if you chose arbitrary node
# because you need to check if all nodes in the graph is visited and sort them
for node in g.edges:
if node not in topo_ordering_list:
topo_ordering_list = g.DFSrecu(node, topo_ordering_list)
return topo_ordering_list
if __name__ == "__main__":
g = Graph()
for node in ['S', 'B', 'A', 'C', 'G', 'I', "L", 'D', 'H']:
g.addNode(node)
g.addEdge("S", "A")
g.addEdge("S", "B")
g.addEdge("B", "D")
g.addEdge("D", "H")
g.addEdge("D", "G")
g.addEdge("H", "I")
g.addEdge("I", "L")
g.addEdge("G", "I")
last_path1 = g.topological_sort("D")
last_path2 = g.topological_sort("S")
print("Start From D: ",last_path1)
print("start From S: ",last_path2)
Output:
Start From D: ['L', 'I', 'H', 'G', 'D', 'A', 'B', 'S', 'C']
start From S: ['A', 'L', 'I', 'H', 'G', 'D', 'B', 'S', 'C']
you can see here 'C' is included in topological sorted list even it's not connect to any other node but 'C' in the graph and you need to visited her
that's way you need for loop in topological_sort() function

Python list is not passing as expected while doing recursion

I'm trying to solve theN-Queues
as following:
class Solution(object):
def __init__(self):
self.queues = []
def DFS(self, n, row, col, pie, na, path):
if row == n:
print 'current recursion path: ', path
self.queues.append(path)
print 'paths within the recursion: ', self.queues
return
for c in range(n):
if (c not in col) and (c + row not in pie) and (row-c not in na):
col.add(c)
pie.add(c+row)
na.add(row-c)
# self.queues.append(c)
# path += str(c)
path.append(c)
# print 'row: ', row, 'col: ', c, path
self.DFS(n, row + 1, col, pie, na, path)
col.remove(c)
pie.remove(c+row)
na.remove(row-c)
# path = path[:-1]
path.pop()
# else:
# path.pop()
return None
# print '\n'
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
col = set()
pie = set()
na = set()
print 'before recursion: ', self.queues
self.DFS(n, 0, col, pie, na, [])
print 'after recursion: ', self.queues
# return self.reslutPrint(n)
I got the following print out:
before recursion: []
current recursion path: [1, 3, 0, 2]
paths within the recursion: [[1, 3, 0, 2]]
current recursion path: [2, 0, 3, 1]
paths within the recursion: [[2, 0, 3, 1], [2, 0, 3, 1]]
after recursion: [[], []]
As you can see, the recursion is getting the correct answer for each path, however, the answer is not appended to the global variable self.queues correctly. Can you please let me know why is this happening?
I'm guessing this is due to that when I appended the list to self.queues, it's giving the address instead of the actual value to self.queues. If so, how can I fix this?
Thanks a lot.
Ian
The problem you're having isn't with self.queues, but rather how you're handling path. When you pass path it into a function, you're passing it by assignment, not its value.
Therefore, when you add path to self.queues and later do path.pop(), it is affecting anywhere path is referenced, including in self.queues.
Here's a simple example of the problem:
>>> a = []
>>> b = [1, 2, 3]
>>> a.append(b)
>>> b.pop()
>>> print(a)
[[1, 2]]
So, what's the best way to correct this? You could, as others have suggested, copy your list before appending it, with self.queues.append(path[:]). But I think a closer examination suggests another answer: pass a new path to each new level of the recursion. Rather than path.append(c), try self.DFS(n, row + 1, pie, na, path + [c]. In this way, you're taking a more functional approach of immutability (you could go one step further and enforce immutability by using an immutable data structure, such as tuple). Here's how your code would look with this approach:
def DFS(self, n, row, col, pie, na, path):
if row == n:
print 'current recursion path: ', path
self.queues.append(path)
print 'paths within the recursion: ', self.queues
return
for c in range(n):
if (c not in col) and (c + row not in pie) and (row-c not in na):
col.add(c)
pie.add(c+row)
na.add(row-c)
self.DFS(n, row + 1, col, pie, na, path + [c])
col.remove(c)
pie.remove(c+row)
na.remove(row-c)
You're right, when you append you are putting a list into self.queues, it's via the address. But that's not the issue, the issue is you later go on to modify that same object, and so emptying it also means self.queues holds a pointer to an empty list.
You need to make a copy of the path (eg by self.queues.append(path[:]) variable, and append that to queues. Or in the general case you can use python's copy moduele (either shallow or deep copy depending on your use case https://docs.python.org/2/library/copy.html)

Python list of lists recursion adds extra nesting

I'm trying to solve a similar problem to the one listed here: Python: Combinations of parent-child hierarchy
graph = {}
nodes = [
('top','1a'),
('top','1a1'),
('top','1b'),
('top','1c'),
('1a','2a'),
('1b','2b'),
('1c','2c'),
('2a','3a'),
('2c','3c'),
('3c','4c')
]
for parent,child in nodes:
graph.setdefault(parent,[]).append(child)
def find_all_paths(graph, start, path=[]):
path = path + [start]
if not graph.has_key(start):
return path
paths = []
for node in graph[start]:
paths.append(find_all_paths(graph, node, path))
return paths
test = find_all_paths(graph, 'top')
Desired Output:
[['top', '1a', '2a', '3a'],
['top', '1a1'],
['top', '1b', '2b'],
['top', '1c', '2c', '3c', '4c']]
Actual Output:
[[[['top', '1a', '2a', '3a']]],
['top', '1a1'],
[['top', '1b', '2b']],
[[[['top', '1c', '2c', '3c', '4c']]]]]
Any advice on how I can remove the extra nesting? Thanks!
The following should fix your issue:
def find_all_paths(graph, start, path=None):
if path is None:
# best practice, avoid using [] or {} as
# default parameters as #TigerhawkT3
# pointed out.
path = []
path = path + [start]
if not graph.has_key(start):
return [path] # return the path as a list of lists!
paths = []
for node in graph[start]:
# And now use `extend` to make sure your response
# also ends up as a list of lists
paths.extend(find_all_paths(graph, node, path))
return paths
The issue is confusion between path which is a single list, and paths, which is a list of lists. Your function can return either one, depending on where you are in the graph.
You probably want to return a list of paths in all situations. So change return path in the base case to return [path].
In the recursive case, you now need to deal with merging each child's paths together. I suggest using paths.extend(...) instead of paths.append(...).
Putting that all together, you get:
def find_all_paths(graph, start, path=[]):
path = path + [start]
if not graph.has_key(start):
return [path]
paths = []
for node in graph[start]:
paths.extend(find_all_paths(graph, node, path))
return paths
Here's a non-recursive solution. However, it "cheats" by sorting the output lists.
def find_all_paths(edges):
graph = {}
for u, v in edges:
if u in graph:
graph[v] = graph[u] + [v]
del graph[u]
else:
graph[v] = [u, v]
return sorted(graph.values())
data = (
('top','1a'),
('top','1a1'),
('top','1b'),
('top','1c'),
('1a','2a'),
('1b','2b'),
('1c','2c'),
('2a','3a'),
('2c','3c'),
('3c','4c'),
)
test = find_all_paths(data)
for row in test:
print(row)
output
['top', '1a', '2a', '3a']
['top', '1a1']
['top', '1b', '2b']
['top', '1c', '2c', '3c', '4c']

How to perform recursion in python dictionary

I have a dictionary which I would like to use to create a tree. The idea is to get the value of the index specified, append it to a list. Use this value as index in the next item of the dictionary and the process repeats until when we get a None
My dictionary
dict = {
'A' : 'AF',
'BF': 'B',
'AF': 'Z',
'Z' : None,
'B' : 'B'
}
I can loop through the dict and get the first value, but I can't a better way of loop recursively through the dict.
Note x is my index parameter I would like to specify.i.e A,BF,AF,Z or B
def tree(x,dict):
result = []
for value in dict:
result.append(value)
#stuck somewhere here.
#I would like to use this value as an index again and pick next value.
#Do this until I have no further relation
#print final results in a list
print result
When tree(x,dict) is called, take x = 'A' the expected result should be:
['A','AF','Z']
Thank you for your assistance and contribution.
The non-recursive version is much faster but there's one that looks nice
>>> def tree(D, x):
if x is None:
return []
else:
return [x] + tree(D, D[x])
>>> tree(D, 'A')
['A', 'AF', 'Z']
Or as a one-liner:
def tree(D, x):
return [] if x is None else [x] + tree(D, D[x])
This will have quadratic runtime since it adds two lists each time, although if you want performance you would just use .append and then it would be much more practical to just use a loop anyway.
def tree(x,dict):
old_value = x
while True:
value = dict.get(old_value)
if not value:
break
result.append(value)
print result
You could also try a recursive generator:
# This will loop "forever"
data = {
'A' : 'AF',
'BF': 'B',
'AF': 'Z',
'Z' : None,
'B' : 'B'
}
def tree(key):
value = data.get(key)
yield key
if value is not None:
for value in tree(value):
yield value
for value in tree("A"):
# Do something with the value
EDIT: the suggested approach above fails to detect cycles and will loop until maximum recursion depth is reached.
The recursive approach below keeps track of visited nodes to detect cycles and exits if so. The most comprehensible description of how to find cycles is from this answer:
data = {
'A' : 'AF',
'BF': 'B',
'AF': 'Z',
'Z' : None,
'B' : 'B'
}
def visit_graph(graph, node, visited_nodes):
print "\tcurrent node: ", node, "\tvisited nodes: ", visited_nodes
# None means we have reached a node that doesn't have any children
if node is None:
return visited_nodes
# The current node has already been seen, the graph has a cycle we must exit
if node in visited_nodes:
raise Exception("graph contains a cycle")
# Add the current node to the list of visited node to avoid cycles
visited_nodes.append(node)
# Recursively call the method with the child node of the current node
return visit_graph(graph, graph.get(node), visited_nodes)
# "A" does not generate any cycle
print visit_graph(data, "A", [])
# Starting at "B" or "BF" will generate cycles
try:
print visit_graph(data, "B", [])
except Exception, e:
print e
try:
print visit_graph(data, "BF", [])
except Exception, e:
print e

Categories

Resources