Python - Dijsktra's Algorithm Distance Problem - python

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.

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!

Store all paths from start to end node using BFS

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)

Inserting value into Binary Tree in Python

I need to add values to my Binary Tree (Node).
Here is my class:
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
def __str__(self):
return "{}".format(self.val)
file = open("/home/dzierzen/Downloads/text.txt", "r")
lines = []
for line in file:
cleaned_line = re.sub(r"\s+", "", line)
#
I have txt file with something like this. L means Left, Right mean Right on the tree. Of course the real txt file contains much more records. My questions is: how to deal with that? How to add this values to the tree?
G RR
A
C L
F LLR
X LLL
F R
X RL
H LLG RR
C L
F LLR
X LLL
F R
X RL
H LL
I'm building the tree by going through the list and deciding on the L/R characters which way to go. When I found a non existing leaf, I create a Node there with the value of None. Multiple visits to the same leaf will overwrite the value, but you can easily change that. If the splitted text has only one value, that will be the root Node's value.
I added a printing method that will traverse the tree and print the root of the subtree, then the left and right leaves. It also indents it according to it's level.
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
def __str__(self):
return "{}".format(self.val)
def _printTree(self, node, level=0):
if node != None:
print(f"{'-'*level}{node}")
self._printTree(node.left,level+1)
self._printTree(node.right,level+1)
def printTree(self):
self._printTree(self)
p = s.split('\n')
root = Node(None)
for k in p:
v = k.split()
if len(v) == 1:
root.val = v[0]
else:
r = root
for c in v[1]:
if c == 'L':
if r.left is None:
r.left = Node(None)
r = r.left
elif c == 'R':
if r.right is None:
r.right = Node(None)
r = r.right
r.val = v[0]
root.printTree()
Using the input text you wrote this is what gets generated:
A
-C
--H
---X
---F
-F
--X
--G

Python Trees: Modifying a tree

This is my python code to make an Ordered Binary Decision Diagram (not very relevant for the context). So I just have a tree of a particular height, and I need to set some of the leaf nodes to one. So I have a variable path which involves an array of "decisions", to go left or right from that particular node. But my code is by mistake modifying multiple roots. I am fairly new to Python and I used to rely on pointers when I used C.
def makeCubes(arr):
ans = []
for ar in arr:
ar2 = [ar[i:i + 2] for i in range(0, len(ar), 2)]
#splitting into segments of 2 each
if not '00' in ar2:
ans += [ar2]
return ans
class Node:
def __init__(self,key):
self.key = key
self.left = None
self.right = None
def addLeft(self,node):
self.left = node
def addRight(self,node):
self.right = node
def makeTree(size):
if(size == 1):
leaf = Node('x0')
leaf.addLeft(Node('zero'))
leaf.addRight(Node('zero'))
return leaf
else:
node = Node('x'+str(size-1))
childNode = makeTree(size-1)
node.addLeft(childNode)
node.addRight(childNode)
return node
def inOrder(root):
if(root != None):
return inOrder(root.left) + [root.key] + inOrder(root.right)
return []
def makeOBDD(array):
maxLen = max([len(word) for word in array])
tree = makeTree(maxLen)
for cube in array:
tree = makeOne(tree,cube)
return tree
def makeOne(root,cube):
print("cube",cube)
if(cube == []):
print("updated")
root.key = 'one'
else:
element = cube[0]
if(element == '01'):
root.addLeft(makeOne(root.left,cube[1:]))
elif(element == '10'):
root.addRight(makeOne(root.right,cube[1:]))
return root
# ab + a'b'
'''
Expected output
x1
/ \
x0 x0
/ \ / \
1 0 0 1
'''
cubeSet = ['1010','0101']
cubes = makeCubes(cubeSet)
print(cubes)
obdd = makeOBDD(cubes)
print(inOrder(obdd))

Python - Passing Function Arguments

I am struggling on how to work out how I pass arguments from a function so that I can populate a list in another function - my code is:
infinity = 1000000
invalid_node = -1
startNode = 0
#Values to assign to each node
class Node:
distFromSource = infinity
previous = invalid_node
visited = False
#read in all network nodes
def network():
f = open ('network.txt', 'r')
theNetwork = [[int(node) for node in line.split(',')] for line in f.readlines()]
print theNetwork
return theNetwork
#for each node assign default values
def populateNodeTable():
nodeTable = []
index = 0
f = open('network.txt', 'r')
for line in f:
node = map(int, line.split(','))
nodeTable.append(Node())
print "The previous node is " ,nodeTable[index].previous
print "The distance from source is " ,nodeTable[index].distFromSource
index +=1
nodeTable[startNode].distFromSource = 0
return nodeTable
#find the nearest neighbour to a particular node
def nearestNeighbour(currentNode, theNetwork):
nearestNeighbour = []
nodeIndex = 0
for node in nodeTable:
if node != 0 and currentNode.visited == false:
nearestNeighbour.append(nodeIndex)
nodeIndex +=1
return nearestNeighbour
currentNode = startNode
if __name__ == "__main__":
nodeTable = populateNodeTable()
theNetwork = network()
nearestNeighbour(currentNode, theNetwork)
So, I am trying to fill the nearestNeighbour list in my nearestNeighbour function with a list of nodes nearest to the other nodes. Now, the all the other functions work correctly, with all argument passing functioning as it should.
However, my nearestNeighbour function throws up this error message:
if node != 0 and
theNetwork[currentNode].visited ==
false: AttributeError: 'list' object
has no attribute 'visited'
(Apologies for the layout, haven't quite fathomed the use of the code quotes yet)
class Node(object):
def __init__(self, me, dists):
super(Node,self).__init__()
self.me = me
self.dists = dists
_inf = Network.INF
self.neighbors = sorted((i for i,dist in enumerate(self.dists) if i!=me and dist!=_inf), key=dists.__getitem__)
self.clear()
def clear(self):
self.dist = None
self.prev = None
def nearestNeighbor(self):
try:
return self.neighbors[0]
except IndexError:
return None
def __str__(self):
return "{0}: {1}".format(self.me, self.dists)
class Network(object):
INF = 10**6
#classmethod
def fromFile(cls, fname, delim=None):
with open(fname) as inf:
return cls([[int(dist) for dist in line.split(delim)] for line in inf])
def __init__(self, distArray):
super(Network,self).__init__()
self.nodes = [Node(me,dists) for me,dists in enumerate(distArray)]
def __str__(self):
return '\n'.join(self.nodes)
def floodFill(self, fromNode):
_nodes = self.nodes
for n in _nodes:
n.clear()
_nodes[fromNode].dist = 0
# left as an exercise ;-)
def distances(self):
return [n.dist for n in self.nodes]
def main():
nw = Network.fromFile('network.txt', delim=',')
print(nw)
nw.floodFill(fromNode=0)
print(nw.distances())
if __name__=="__main__":
main()
That's because theNetwork[currentNode] returns a list. In other words: theNetwork is a list of lists.
This is the line where it is done:
theNetwork = [[int(node) for node in line.split(',')] for line in f.readlines()]
theNetwork = [[int(node) for node in line.split(',')] for line in f.readlines()]
theNetwork is a list of lists. A list (theNetwork[currentNode]) doesn't have a visited attribute.
Perhaps you intended something like:
for line in f.readlines():
theNetwork.extend((int(node) for node in line.split(',')))

Categories