Store all paths from start to end node using BFS - python

I have the implemetation of BFS but it stores only one path. How can I modify this code to store all paths from a starting to node to the end. Any ideas?
def BFS(G, user1, user2):
path = []
for v in G:
v.setDistance(0)
v.setPred(None)
vertQueue = Queue()
vertQueue.enqueue(G.getVertex(user1))
while vertQueue.size() > 0:
currentVert = vertQueue.dequeue()
for nbr in currentVert.getConnections():
if nbr.getColor() == 'white':
nbr.setColor('gray')
nbr.setDistance(currentVert.getDistance() + 1)
nbr.setPred(currentVert)
vertQueue.enqueue(nbr)
currentVert.setColor('black')
prev = G.getVertex(user2)
while prev.getPred():
path.append(prev.getPred().getId())
prev = prev.getPred()
path = path[::-1]
path.append(user2)
return ' -> '.join(path)

Related

Solving Dijkstra's algorithm - passing costs / parents with two edges

I have a graph like this:
# graph table
graph = {}
graph['start'] = {}
graph['start']['a'] = 5
graph['start']['b'] = 2
graph['a'] = {}
graph['a']['c'] = 4
graph['a']['d'] = 2
graph['b'] = {}
graph['b']['a'] = 8
graph['b']['d'] = 7
graph['c'] = {}
graph['c']['d'] = 6
graph['c']['finish'] = 3
graph['d'] = {}
graph['d']['finish'] = 1
graph['finish'] = {}
And I am trying to find the fastest way from S to F.
In the first example in the book only one edge was connected to one node, in this example for example, node D has 3 weights and a cost table was used:
costs = {}
infinity = float('inf')
costs['a'] = 5
costs['b'] = 2
costs['c'] = 4
costs['d'] = # there is 3 costs to node D, which one to select?
costs['finish'] = infinity
And a parents table:
parents = {}
parents['a'] = 'start' # why not start and b since both `S` and `B` can be `A` nodes parent?
parents['b'] = 'start'
parents['c'] = 'a'
parents['d'] = # node D can have 3 parents
parents['finish'] = None
But this also works, by works I mean no error is thrown, so do I only have to name the parents from the first node S?
parents = {}
parents['a'] = 'start'
parents['b'] = 'start'
parents['finish'] = None
The code:
processed = []
def find_lowest_cost_node(costs):
lowest_cost = float('inf')
lowest_cost_node = None
for node in costs:
cost = costs[node]
if cost < lowest_cost and node not in processed:
lowest_cost = cost
lowest_cost_node = node
return lowest_cost_node
node = find_lowest_cost_node(costs)
while node is not None:
cost = costs[node]
neighbors = graph[node]
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if costs[n] > new_cost:
costs[n] = new_cost
parents[n] = node
processed.append(node)
node = find_lowest_cost_node(costs)
def find_path(parents, finish):
path = []
node = finish
while node:
path.insert(0, node)
if parents.__contains__(node):
node = parents[node]
else:
node = None
return path
path = find_path(parents, 'finish')
distance = costs['finish']
print(f'Path is: {path}')
print(f'Distance from start to finish is: {distance}')
I get:
Path is: ['finish']
Distance from start to finish is: inf
Where is my mistake and how should I add cost and parent to a node which can be visited from more than 1 node?
Edit
I do believe this is not the best approach for this problem, the best in practice solution / recommendations are welcome.
You do not need to initialise the cost table with more than costs['start'] = 0 or the parents dictionary with more than parents = {}. That is what your algorithm is going to create for you!
The only other change you need to make is to your while loop. It just needs to check whether the new node has already been detected before. If so then we check to see whether the new path is shorter and update as required; if not then we establish the new path.
while node is not None:
cost = costs[node]
neighbors = graph[node]
for n in neighbors.keys():
new_cost = cost + neighbors[n]
if n in costs:
if costs[n] > new_cost:
costs[n] = new_cost
parents[n] = node
else:
costs[n] = new_cost
parents[n] = node
processed.append(node)
node = find_lowest_cost_node(costs)
I think there are much neater ways to deal with graphs but this is the minimal change needed to make your code work as required. Hope it's helpful!

'<' not supported between instances of 'node' and 'node' priorityqueue python

I am implementing A* Search algorithm in Python.
for get the min edge node, i am using priority queue class of python. the algorithm implementation portion.
the code -
from queue import PriorityQueue
def a_star_search(g_edge, h_value, des):
path = []
class node:
def __init__(self, node_no, prev, actual_cost, total_cost):
self.node_no = node_no
self.prev = prev
self.actual_cost = actual_cost
self.total_cost = total_cost
minQ = PriorityQueue()
new = node(0, None, 0, 10)
minQ.put(new)
path.append(0)
while not minQ.empty():
NOb = minQ.get()
if NOb.node_no == des:
return path
for list in g_edge:
for element in list:
if element[0] == NOb.node_no:
prev = NOb.node_no
actual_cost = NOb.actual_cost + element[2]
for l in h_value:
if l[0] == 0:
total_cost = NOb.actual_cost + l[1]
NObNew = node(element[1], prev, actual_cost, total_cost)
minQ.put(NObNew)
path.append(NOb.node_no)
the error occured in this line ->
NObNew = node(element[1], prev, actual_cost, total_cost)
where is the problem?

Bidirectional BFS in Python Implementation

I am implementing a bidirectional BFS for a 2d maze problem in Python 3.7.3.
Firstly, here is how I create the maze:
for row in range(dim):
# Add an empty array that will hold each cell
# in this row
grid.append([])
for column in range(dim):
grid[row].append(np.random.binomial(1, 0.2, 1)) # Append a cell
if (row == column == dim - 1):
grid[row][column] = 0
grid[0][0] = 0
Now, in order to facilitate the bidirectional BFS, I defined a junct class which is essentially a node which stores both a parent junct from the starting direction and a parent junct from the goal direction.
class junct():
def __init__(self, row, column, parent_S, parent_G):
self.row = row
self.column = column
self.parent_S = parent_S
self.parent_G = parent_G
def __eq__(self, other):
return (self.row == other.row and self.column == other.column)
Here is my bidirectional bfs function. It is part of an Agent class which has not given me any issues so far. I want this function to return a list of tuples (row, column) of all the nodes in the path from the start to the goal.
def bidirectional_bfs(self):
def get_value(maze, a, b):
return maze[a][b]
def is_out_of_bounds(a, b, d):
return (a < 0 or a >= dim) or (b < 0 or b >= d)
grid = self.grid
dim = len(grid)
Q_start = []
Q_goal = []
visited = []
start = junct(0, 0, None, None)
goal = junct(dim-1, dim-1, None, None)
Q_start.append(start)
Q_goal.append(goal)
visited.append(start)
visited.append(goal)
#beginning loop
while (len(Q_start) > 0) and (len(Q_goal) > 0):
#initializations
current_S = Q_start[0]
current_G = Q_goal[0]
row_S = current_S.row
column_S = current_S.column
row_G = current_G.row
column_G = current_G.column
#some mechanics
if len(Q_start) > 0:
Q_start.pop(0)
if len(Q_goal) > 0:
Q_goal.pop(0)
#in case the current node from starting is in the goal Queue
if (current_S in Q_goal):
#forming the path back to G
current = current_S
path_S = [current]
while current.parent_S is not None:
path_S.append(current.parent_S)
current = current.parent_S
path_S = [(item.row, item.column) for item in path_S]
print(path_S)
#in case the current node from goal is in the start Queue
if (current_G in Q_start):
#forming the path back to S
current = current_G
path_G = [currrent]
while current.parent_S is not None:
path_G.append(current.parent_G)
current = current.parent_G
path_G = [(item.row, item.column) for item in path_G]
print(path_G)
if (current_S in Q_goal) and (current_G in Q_start):
path = [item for item in path_G for item in path_S]
print(path)
return path
#enqueueing children from the start direction
children_S = [junct(row_S+1, column_S, current_S, None), junct(row_S-1, column_S, current_S, None), junct(row_S, column_S+1, current_S, None), junct(row_S, column_S-1, current_S, None)]
for child in children_S:
if not is_out_of_bounds(child.row, child.column, dim):
if child not in visited:
if get_value(grid, child.row, child.column) == 0:
Q_start.append(child)
visited.append(child)
#enqueueing childen from the goal direction
#enqueueing children from the start direction
children_G = [junct(row_G+1, column_G, None, current_G), junct(row_G-1, column_G, None, current_G), junct(row_G, column_G+1, None, current_G), junct(row_G, column_G-1, None, current_G)]
for child in children_S:
if not is_out_of_bounds(child.row, child.column, dim):
if child not in visited:
if get_value(grid, child.row, child.column) == 0:
Q_goal.append(child)
visited.append(child)
return []
print("No path")
Where am I going wrong in my code? What needs to be changed in order to return the path?

Adding to list a class instance

I'm implementing a code to find the shortest path between two nodes, but
why when I change the first line of the DFS function the output change too .
Isn't it true that
path += [start] is equivalent to path = path + [start]?
the output before changing is ::
Current DFS path: 0
Current DFS path: 0->1
Current DFS path: 0->1->2
Current DFS path: 0->1->2->3
Current DFS path: 0->1->2->3->4
Current DFS path: 0->1->2->3->5
Current DFS path: 0->1->2->4
Current DFS path: 0->2
Current DFS path: 0->2->3
Current DFS path: 0->2->3->1
Current DFS path: 0->2->3->4
Current DFS path: 0->2->3->5
Current DFS path: 0->2->4
shortest path is 0->2->3->5
after changing is ::
Current DFS path: 0
Current DFS path: 0->1
Current DFS path: 0->1->2
Current DFS path: 0->1->2->3
Current DFS path: 0->1->2->3->4
Current DFS path: 0->1->2->3->4->5
shortest path is 0->1->2->3->4->5
The code ::
class Node(object):
def __init__(self, name):
"""Assumes name is a string"""
self.name = name
def getName(self):
return self.name
def __str__(self):
return self.name
class Edge(object):
def __init__(self, src, dest):
"""Assumes src and dest are nodes"""
self.src = src
self.dest = dest
def getSource(self):
return self.src
def getDestination(self):
return self.dest
def __str__(self):
return self.src.getName() + '->' + self.dest.getName()
class WeightedEdge(Edge):
def __init__(self, src, dest, weight = 1.0):
"""Assumes src and dest are nodes, weight a number"""
self.src = src
self.dest = dest
self.weight = weight
def getWeight(self):
return self.weight
def __str__(self):
return self.src.getName() + '->(' + str(self.weight) + ')'\
+ self.dest.getName()
#Figure 12.8
class Digraph(object):
#nodes is a list of the nodes in the graph
#edges is a dict mapping each node to a list of its children
def __init__(self):
self.nodes = []
self.edges = {}
def addNode(self, node):
if node in self.nodes:
raise ValueError('Duplicate node')
else:
self.nodes.append(node)
self.edges[node] = []
def addEdge(self, edge):
src = edge.getSource()
dest = edge.getDestination()
if not (src in self.nodes and dest in self.nodes):
raise ValueError('Node not in graph')
self.edges[src].append(dest)
def childrenOf(self, node):
return self.edges[node]
def hasNode(self, node):
return node in self.nodes
def __str__(self):
result = ''
for src in self.nodes:
for dest in self.edges[src]:
result = result + src.getName() + '->'\
+ dest.getName() + '\n'
return result[:-1] #omit final newline
class Graph(Digraph):
def addEdge(self, edge):
Digraph.addEdge(self, edge)
rev = Edge(edge.getDestination(), edge.getSource())
Digraph.addEdge(self, rev)
#Figure 12.9
def printPath(path):
"""Assumes path is a list of nodes"""
result = ''
for i in range(len(path)):
result = result + str(path[i])
if i != len(path) - 1:
result = result + '->'
return result
def DFS(graph, start, end, path, shortest, toPrint = False):
"""Assumes graph is a Digraph; start and end are nodes;
path and shortest are lists of nodes
Returns a shortest path from start to end in graph"""
path = path + [start]
if toPrint:
print('Current DFS path:', printPath(path))
if start == end:
return path
for node in graph.childrenOf(start):
if node not in path: #avoid cycles
if shortest == None or len(path) < len(shortest):
newPath = DFS(graph, node, end, path, shortest,
toPrint)
if newPath != None:
shortest = newPath
return shortest
def shortestPath(graph, start, end, toPrint = False):
"""Assumes graph is a Digraph; start and end are nodes
Returns a shortest path from start to end in graph"""
return DFS(graph, start, end, [], None, toPrint)
#Figure 12.10
def testSP():
nodes = []
for name in range(6): #Create 6 nodes
nodes.append(Node(str(name)))
g = Digraph()
for n in nodes:
g.addNode(n)
g.addEdge(Edge(nodes[0],nodes[1]))
g.addEdge(Edge(nodes[1],nodes[2]))
g.addEdge(Edge(nodes[2],nodes[3]))
g.addEdge(Edge(nodes[2],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[5]))
g.addEdge(Edge(nodes[0],nodes[2]))
g.addEdge(Edge(nodes[1],nodes[0]))
g.addEdge(Edge(nodes[3],nodes[1]))
g.addEdge(Edge(nodes[4],nodes[0]))
sp = shortestPath(g, nodes[0], nodes[5])
print('Shortest path found by DFS:', printPath(sp))
Note :: this code is from this book enter link description here
They are not the same
path += [start] is equivalent to path.extend([start]) -- it mutates path.
On the other hand
path = path + [start] creates a new list and names it start.
Consider the following experiment, and note the IDs:
>>> a = [1]
>>> id(a)
55937672
>>> a += [2,3]
>>> id(a)
55937672
>>> b = [1]
>>> id(b)
55930440
>>> b = b + [1,2]
>>> id(b)
55937288
The ID of b changed but the ID of a didn't.
As to why it makes a difference in your code -- DFS is a function. In the version which uses path += [start], you are modifying the passed parameter path -- and this modification persists after the call returns. On the other hand, in the version which uses path = path + [start], you are creating a new local variable named path, one which goes out of scope when the call returns, without any changes to the parameter path.
In line
path=path+[start]
you create new list object.
In line
path+=[start]
you modify list object that already exists.
You can try this:
path2=path[:]
path2+=[start]

Python - Dijsktra's Algorithm Distance Problem

I've run into a problem with my code, i'm not able to calculate the distance to a node from the starting node. I have a text file of the form:
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9
This represents the node distances in the graph. Here is my code, unfortunately, despite trying a few different methods I still keep coming up with various error messages.
infinity = 1000000
invalid_node = -1
startNode = 0
class Node:
distFromSource = infinity
previous = invalid_node
visited = False
def populateNodeTable():
nodeTable = []
index =0
f = open('route.txt', 'r')
for line in f:
node = map(int, line.split(','))
nodeTable.append(Node())
print nodeTable[index].previous
print nodeTable[index].distFromSource
index +=1
nodeTable[startNode].distFromSource = 0
return nodeTable
def tentativeDistance(currentNode, nodeTable):
nearestNeighbour = []
for currentNode in nodeTable:
# if Node[currentNode].distFromSource + currentDistance = startNode + currentNode
# currentDistance = currentNode.distFromSource + nodeTable.currentNode
currentNode.previous = currentNode
currentNode.length = currentDistance
currentNode.visited = True
currentNode +=1
nearestNeighbour.append(currentNode)
print nearestNeighbour
return nearestNeighbour
def shortestPath (nearestNeighbour)
shortestPath = []
f = open ('spf.txt', 'r')
f.close()
currentNode = startNode
if __name__ == "__main__":
populateNodeTable()
tentativeDistance(currentNode,populateNodeTable())
The lines starting with '#' in my tentativeDistance function is the section giving me trouble. I've looked at some other implementations on the web though they confuse me
I have been programming the Dijkstra's Algorithm in Python a few months ago; its tested and it should work:
def dijkstra(u,graph):
n = graph.numNodes
l = { u : 0 } ; W = graph.V()
F = [] ; k = {}
for i in range(0,n):
lv,v = min([ (l[lk],lk) for lk in l.keys() if lk in W ])
W.remove(v)
if v!=u: F.append(k[v])
for v1 in [ v2 for v2 in graph.G(v) if v2 in W ]:
if v1 not in l or l[v]+graph.w(v,v1) < l[v1]:
l[v1] = l[v] + graph.w(v,v1)
k[v1] = (v,v1)
return l,F
You need a class Graph with Method V() (which yields the graphs nodes), w(v1,v2) (which yields the weight of the edge (v1,v2)), remove (which removes an edge from a graph) and attribute numNodes (which yields the number of nodes in the graph) and G(v) which yields the neighborhood of node v.

Categories