I am trying to write Dijkstra's Algorithm, however I am struggling on how to 'say' certain things in code.
To visualize, here are the columns I want represented using arrays:
max_nodes
A B C Length Predecessor Visited/Unvisited
A 0 1 2 -1 U
B 1 0 1 -1 U
C 2 1 0 -1 U
So, there will be several arrays, as seen in my code below:
def dijkstra (graph, start, end)
network[max_nodes][max_nodes]
state [max_nodes][length]
state2 [max_nodes][predecessor]
state3 [max_nodes][visited]
initialNode = 0
for nodes in graph:
D[max_nodes][length] = -1
P[max_nodes][predecessor] = ""
V[max_nodes][visited] = false
for l in graph:
length = lengthFromSource[node] + graph[node][l]
if length < lengthFromSourceNode[w]:
state[l][length] = x
state2[l][predecessor]
state3[l][visited] = true
x +=1
The part in bold is where I am stuck on - I am trying to implement this section of the algorithm:
3. For current node, consider all its unvisited neighbors and calculate their tentative distance. For example, if current node (A) has distance of 6, and an edge connecting it with another node (B) is 2, the distance to B through A will be 6+2=8. If this distance is less than the previously recorded distance, overwrite the distance
4. When we are done considering all neighbors of the current node, mark it as visited. A visited node will not be checked ever again; its distance recorded now is final and minimal
I think I am on the right track, i'm just stuck on how to say 'start at a node, get the length from source to a node, if length is smaller, overwrite previous value, then move to next node
I also used a dictionary to store the network.
Data is in the following format:
source: {destination: cost}
create a network dictionary (user provided)
net = {'0':{'1':100, '2':300},
'1':{'3':500, '4':500, '5':100},
'2':{'4':100, '5':100},
'3':{'5':20},
'4':{'5':20},
'5':{}
}
shortest path algorithm (user needs to specify start and terminal nodes)
def dijkstra(net, s, t):
# sanity check
if s == t:
return "The start and terminal nodes are the same. Minimum distance is 0."
if s not in net: # python2: if net.has_key(s)==False:
return "There is no start node called " + str(s) + "."
if t not in net: # python2: if net.has_key(t)==False:
return "There is no terminal node called " + str(t) + "."
# create a labels dictionary
labels={}
# record whether a label was updated
order={}
# populate an initial labels dictionary
for i in net.keys():
if i == s: labels[i] = 0 # shortest distance form s to s is 0
else: labels[i] = float("inf") # initial labels are infinity
from copy import copy
drop1 = copy(labels) # used for looping
## begin algorithm
while len(drop1) > 0:
# find the key with the lowest label
minNode = min(drop1, key = drop1.get) #minNode is the node with the smallest label
# update labels for nodes that are connected to minNode
for i in net[minNode]:
if labels[i] > (labels[minNode] + net[minNode][i]):
labels[i] = labels[minNode] + net[minNode][i]
drop1[i] = labels[minNode] + net[minNode][i]
order[i] = minNode
del drop1[minNode] # once a node has been visited, it's excluded from drop1
## end algorithm
# print shortest path
temp = copy(t)
rpath = []
path = []
while 1:
rpath.append(temp)
if temp in order: temp = order[temp] #if order.has_key(temp): temp = order[temp]
else: return "There is no path from " + str(s) + " to " + str(t) + "."
if temp == s:
rpath.append(temp)
break
for j in range(len(rpath)-1,-1,-1):
path.append(rpath[j])
return "The shortest path from " + s + " to " + t + " is " + str(path) + ". Minimum distance is " + str(labels[t]) + "."
# Given a large random network find the shortest path from '0' to '5'
print dijkstra(net, s='0', t='5')
First, I assume this is a homework problem, as the best suggest is to not bother writing it yourself, but to find an existing implementation on the web. Here's one that looks pretty good, for example.
Assuming you do need to reinvent the wheel, the code referenced there uses dictionaries to store the node data. So you feed it something like:
{
's': {'u' : 10, 'x' : 5},
'u': {'v' : 1, 'x' : 2},
'v': {'y' : 4},
'x': {'u' : 3, 'v' : 9, 'y' : 2},
'y': {'s' : 7, 'v' : 6}
}
This seems a more intuitive way of presenting your graph information. Visited nodes and distances can be kept in dictionaries as well.
Related
I'm trying to implement Dijkstra's algorithm to find the shortest distances of all vertices from the source vertex 1.
This is my implementation:
from ast import literal_eval
p = {}
with open(r'dijk.txt') as file:
for line in file:
line_content = line.split()
p[int(line_content[0])] = [literal_eval(edge) for edge in line_content[1:]]
source_vertex = next(iter(p.keys()))
print("Vertex 1's paths:", p[1])
print("1st value for vertex 1:", p[1][0])
print("1st neighbour of vertex 1:",p[1][0][0])
print("Distance of 1st neighbour of vertex 1:",p[1][0][1])
X = [1]
Y = [1]
A = {}
A[1] = 0
for i in range(2,201):
A[i] = 100000000
Y.append(i)
while X != Y:
b = {}
for eachvertex in X:
b2 = 0
for eachneighbour in p[eachvertex]:
if eachneighbour[0] not in X:
b2 = A[eachvertex]+eachneighbour[1]
b[eachneighbour[0]] = b2
vertex_to_be_added = min(b, key = b.get)
X.append(vertex_to_be_added)
A[vertex_to_be_added] = min(b.values())
print("Vertex added :",vertex_to_be_added, "and distance :",min(b.values()))
X.sort()
The data source can be found here :
LinkToData
It works for the first iteration of the while loop, but after that the distances calculated for subsequent nodes/vertices added are not the shortest distance. Where am I going wrong ?
Tree difference (Problem Code: TREDIFF). This is a problem from Codechef's May Lunchtime (Contest is over so Practice now). My code runs fine in other IDEs but I get a TLE error when I submit. I code in Python 3. But all help is appreciated. Here's the problem:
Problem
You are given a tree with N nodes (numbered 1 through N). For each valid i, node i has a value Ai.
You should answer Q queries. In each query:
You are given two nodes a and b.
Let S denote the set of all nodes on the simple path between the nodes a and b (including these nodes).
Find the minimum value of |Ax−Ay| over all pairs x,y∈S such that x≠y.
Input
The first line of the input contains a single integer T denoting the number of test cases. The description of T test cases follows.
The first line of each test case contains two space-separated integers N and Q.
The second line contains N space-separated integers A1,A2,…,AN.
Each of the next N−1 lines contains two space-separated integers u and v denoting that nodes u and v are connected by an edge.
Each of the last Q lines contains two space-separated integers a and b describing a query.
Output
Output
For each query, print a single line containing one integer ― the answer to the query.
Does anyone have an idea on how to make it faster?
from collections import deque
INFINITY = float("inf")
Zero = 0
class Graph:
def __init__(self, datasheet):
graph_edges = datasheet
self.nodes = set()
for edge in graph_edges:
self.nodes.update([edge[0], edge[1]])
self.adjacency_list = {node: set() for node in self.nodes}
for edge in graph_edges:
self.adjacency_list[edge[0]].add((edge[1], edge[2]))
def shortest_path(self, start_node, end_node):
unvisited_nodes = self.nodes.copy()
distance_from_start = {
node: (0 if node == start_node else INFINITY) for node in self.nodes
}
previous_node = {node: None for node in self.nodes}
while unvisited_nodes:
current_node = min(
unvisited_nodes, key=lambda node: distance_from_start[node]
)
unvisited_nodes.remove(current_node)
if distance_from_start[current_node] == INFINITY:
break
for neighbor, distance in self.adjacency_list[current_node]:
new_path = distance_from_start[current_node] + distance
if new_path < distance_from_start[neighbor]:
distance_from_start[neighbor] = new_path
previous_node[neighbor] = current_node
if current_node == end_node:
break
path = deque()
current_node = end_node
while previous_node[current_node] is not None:
path.appendleft(current_node)
current_node = previous_node[current_node]
path.appendleft(start_node)
return path, distance_from_start[end_node]
T = int(input())
for k in range(T):
N, Q = map(int, input().split())
Values = list(map(int, input().split()))
nodes = []
queries = []
datasheet = []
for i in range(N - 1):
a, b = map(int, input().split())
cost = Values[b - 1] - Values[a - 1]
datasheet.append((a, b, float(cost)))
for j in range(Q):
c = list(map(int, input().split()))
start = c[0]
end = c[1]
queries.append(c)
graph = Graph(datasheet=datasheet)
returned_path, returned_distance = graph.shortest_path(start, end)
if returned_distance == INFINITY:
print(Zero)
else:
print(int(abs(returned_distance)))
I have a basic maze game in python "*" are walls, "W" water buckets which allow you to travel through fire "F" (if you have none you lose), "A" is the player and " " is air and "X" and "Y" are start and endpoints. There are also numbers from 1 - 9 which have 1 other matching number so the player can teleport. Example map:
*X**********
* 1 W F*
* FFFF 1 *
**********Y*
So I have a graph (dictionary) where I go through each character in the maze and find their neighbours like {"0,0": [None, "0,1", None, "0,1"]}
{"node": "LeftNeighbour", "RightNeighbour", "NeighbourAbove", "NeighbourBelow"}
and I am using coordinates "x,y" to denote neighbours.
Then the DFS code is ...
visited = []
def DFS_recursive(graph, node, nodeToFind):
global visited
for neighbour in graph[node]:
if neighbour == nodeToFind:
visited.append(neighbour)
break
if neighbour not in visited and neighbour != None:
# get x and y by breaking up the string
x = ""
y = ""
yFlag = False
for char in neighbour:
if char == ",":
yFlag = True
elif yFlag == False:
x += str(char)
else:
y += str(char)
x, y = int(x), int(y)
if grid[y][x].display == "*": # if the neighbour's display property is a wall, append the neighbour but don't call the function again ...
visited.append(neighbour)
else:
visited.append(neighbour)
DFS_recursive(graph, neighbour, nodeToFind)
So this works until I add teleport pad code.
if neighbour == (number from 1 - 9):
find the coordinates of the other teleport pad and make the coordinates the new node
DFS_recursive(graph, newNode, nodeToFind)
And yes I haven't added in the "nodeToFind" bit yet, couldn't quite get that to work so I added a separate function to deal with that. Probably should remove the parameter but anyway that's not the point.
So 1. Not sure if this is the right way to go about it (am new to programming)
and 2. No idea how to handle the teleport pads.
Thanks in advance!
I am trying to discover how to use for loops and where strings and ints can differentiate.
I created a function calling dimension and TV size: for example
def TVDisplay(dimension, TVsize):
final = "<==="
for i in range(TVsize-2):
final = final + "=="
final = final + "==>\n"
for corner in dimension:
final = final + "< "
for edge in corner:
final = final + edge + " "
final = final + ">\n"
final = final + "<==="
for i in range(TVsize-2):
final = final + "=="
final = final + "==>\n"
return final
This function returns
<=====>
< 0 0 >
< 0 0 >
<=====>
Based on a dimension that is [['0','0'],['0','0']] and a TVsize of 2.
Now I am trying to use while loops to make it look similar, but I am running into problems at the strings and int()s
My Function looks like this:
def TVDisplay(dimension, TVsize):
final="<==="
i=0
while i < TVsize-2:
final = final + "=="
ctr+=1
final = final + "==>\n"
corner=0
while corner < dimension:
edge = 0
final = final + "< "
while edge < corner:
final = final + edge + " "
edge+=1
final = final + ">\n"
corner+=1
final = final + "<==="
while i < TVsize-2:
final = final + "=="
i+=1
final = final + "==>\n"
return final
This function returns this:
<=====>
<>
< 0 >
<=====>
I think it has to do with my middle part of code that is conflicting with strs or ints.
Does anyone have any advice how to fix this problem?
Thank you!!
EDITED::
corner=1
while corner < dimension:
final = final + "< "
edge = 0
while edge < corner:
final = final + edge + " "
edge+=1
final = final + ">\n"
corner+=1
At the:
final = final + edge + " "
line, cannot concatenate 'str' and 'int' objects appears.
my purpose to get the middle part of the loop is to spit out the middle part of the display
< 0 0 >
< 0 0 >
the last loop closes it off.
so thats my issue
Dimension is a list of lists right?
So when you call:
for corner in dimension:
for edge in corner:
It is referring to the actual object. So corner is a list and edge is an object in that list.
It would be the same as saying:
while corner < len(dimension):
edge = 0
while edge < len(dimension[corner]):
finale += dimension[corner][edge] + " "
The difference is that when you say:
for x in y
You are actually referring to the object x which is in y. However when you say:
while x < y:
x+=1
X is only an integer (in your case it is the index of the object). To access the actual object you must use y[x]. The 'in' function refers to actual objects, whereas when you use a while loop you create a counter that keeps track of an index but not the actual object.
while corner_index < len(dimension):
edge_index = 0
corner = dimension[corner_index] #retrieve the list from dimension
final = final + "< "
while edge_index < len(corner):
edge = corner[edge_index] #get the edge
final += edge + " "
edge_index+=1
final = final + ">\n"
corner_index+=1
To be even more succint:
while corner_index < len(dimension):
edge_index = 0
final = final + "< "
while edge_index < len(dimension[corner_index]):
final += dimension[corner_index][edge_index] + " "
edge_index+=1
final = final + ">\n"
corner_index+=1
As to your edit:
The way you are accessing edge (as an index integer) means you must first typecast to a string. So:
final += str(edge) + " "
You didn't have this issue initially because 'edge' referred to the actual string object '0' in your dimensions list. However, when you use while loops, 'edge' is an integer that you are using as a counter.
I have copied and pasted this answer for Dijkstra Algorithm to my project. It seemed ok after several simple tests.
In my specific implementation, I need the algorithm to return a list of nodes. So I have to modify the original code so that it always returns a list. More specifically, I removed all the return "string" lines there. The modified code by me is as follows:
## using Dijkstra Algorithm ##
def choosePath(s, t):
net = {'0':{'1':138, '9':150},
'1':{'0':138, '2':178, '8':194},
'2':{'1':178, '3':47.5},
'3':{'2':47.5, '4':70},
'4':{'3':70, '5':70},
'5':{'4':70, '6':36},
'6':{'5':36, '7':50},
'7':{'6':50, '8':81},
'8':{'7':81, '9':138, '1':194},
'9':{'8':138, '0':150}}
# sanity check
if s == t:
return []
# create a labels dictionary
labels={}
# record whether a label was updated
order={}
# populate an initial labels dictionary
for i in net.keys():
if i == s: labels[i] = 0 # shortest distance form s to s is 0
else: labels[i] = float("inf") # initial labels are infinity
from copy import copy
drop1 = copy(labels) # used for looping
## begin algorithm
while len(drop1) > 0:
# find the key with the lowest label
minNode = min(drop1, key = drop1.get) #minNode is the node with the smallest label
# update labels for nodes that are connected to minNode
for i in net[minNode]:
if labels[i] > (labels[minNode] + net[minNode][i]):
labels[i] = labels[minNode] + net[minNode][i]
drop1[i] = labels[minNode] + net[minNode][i]
order[i] = minNode
del drop1[minNode] # once a node has been visited, it's excluded from drop1
## end algorithm
# print shortest path
temp = copy(t)
rpath = []
path = []
while 1:
rpath.append(temp)
if order.has_key(temp):
temp = order[temp]
if temp == s:
rpath.append(temp)
break
for j in range(len(rpath)-1,-1,-1):
path.append(rpath[j])
return [junctions[int(elem)] for elem in path]
Then when I run it, I end up with the following error:
>>> Traceback (most recent call last):
File "C:\Users\...\simulation.py", line 162, in choosePath
rpath.append(temp)
MemoryError
Obviously, it is because I have removed the return "string" lines. However, I failed to find out which deletion makes it die. Why is it so?
How may I make it work again AND always returns a list instead of a string as I wish?
I suspect your problem is that you're passing the wrong arguments to the function. You want to call choosePath('0', '9'). Strings. Not integers.
What's comical is that if ANY of the parts of the program you removed were still there, it would have caught this and stopped the program. With this part, it catches if your input is wrong.
if net.has_key(s)==False:
return "There is no start node called " + str(s) + "."
if net.has_key(t)==False:
return "There is no terminal node called " + str(t) + "."
With this part, it catches if it never reaches a solution.
else: return "There is no path from " + str(s) + " to " + str(t) + "."
The sanity checks are not strictly necessary, since as you mentioned a path is assured in your net. Still the checks are nice because if you ever do choose to change things around you'll know the computer will call you out on obvious mistakes. One option is to replace them with exceptions, since none of these messages should really come up unless something has gone horribly wrong. That's what I opted for in the following code.
class NoPathException(Exception):
pass
def choosePath(s, t):
net = {'0':{'1':138, '9':150},
'1':{'0':138, '2':178, '8':194},
'2':{'1':178, '3':47.5},
'3':{'2':47.5, '4':70},
'4':{'3':70, '5':70},
'5':{'4':70, '6':36},
'6':{'5':36, '7':50},
'7':{'6':50, '8':81},
'8':{'7':81, '9':138, '1':194},
'9':{'8':138, '0':150}}
# sanity check
if s == t:
return []
if not net.has_key(s):
raise ValueError("start node argument not in net")
if not net.has_key(t):
raise ValueError("end node argument not in net")
# create a labels dictionary
labels={}
# record whether a label was updated
order={}
# populate an initial labels dictionary
for i in net.keys():
if i == s: labels[i] = 0 # shortest distance form s to s is 0
else: labels[i] = float("inf") # initial labels are infinity
from copy import copy
drop1 = copy(labels) # used for looping
## begin algorithm
while len(drop1) > 0:
# find the key with the lowest label
minNode = min(drop1, key = drop1.get) #minNode is the nod2 with the smallest label
# update labels for nodes that are connected to minNode
for i in net[minNode]:
if labels[i] > (labels[minNode] + net[minNode][i]):
labels[i] = labels[minNode] + net[minNode][i]
drop1[i] = labels[minNode] + net[minNode][i]
order[i] = minNode
del drop1[minNode] # once a node has been visited, it's excluded from drop1
## end algorithm
# print shortest path
temp = copy(t)
rpath = []
path = []
while 1:
rpath.append(temp)
if order.has_key(temp):
temp = order[temp]
else:
raise NoPathException("no path to solution")
if temp == s:
rpath.append(temp)
break
for j in range(len(rpath)-1,-1,-1):
path.append(rpath[j])
return path
Testing
a = choosePath('3', '9')
print(a)
['3', '4', '5', '6', '7', '8', '9']
Is this the output you're looking for?