Increase code coverage for dijkstra algorithm (#10695)

* Increase code coverage for dijkstra algorithm

* Add missing code coverage

Refactor to pass mypy

* Fix missing code coverage

* Remove code changes, keep doctest

* Remove ALL of the code changes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update dijkstra_algorithm.py

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Adam Ross 2023-10-27 22:10:32 +02:00 committed by GitHub
parent 185a35589a
commit b0837d3985
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,35 +11,127 @@ import sys
class PriorityQueue: class PriorityQueue:
# Based on Min Heap # Based on Min Heap
def __init__(self): def __init__(self):
"""
Priority queue class constructor method.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.cur_size
0
>>> priority_queue_test.array
[]
>>> priority_queue_test.pos
{}
"""
self.cur_size = 0 self.cur_size = 0
self.array = [] self.array = []
self.pos = {} # To store the pos of node in array self.pos = {} # To store the pos of node in array
def is_empty(self): def is_empty(self):
"""
Conditional boolean method to determine if the priority queue is empty or not.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.is_empty()
True
>>> priority_queue_test.insert((2, 'A'))
>>> priority_queue_test.is_empty()
False
"""
return self.cur_size == 0 return self.cur_size == 0
def min_heapify(self, idx): def min_heapify(self, idx):
"""
Sorts the queue array so that the minimum element is root.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.cur_size = 3
>>> priority_queue_test.pos = {'A': 0, 'B': 1, 'C': 2}
>>> priority_queue_test.array = [(5, 'A'), (10, 'B'), (15, 'C')]
>>> priority_queue_test.min_heapify(0)
Traceback (most recent call last):
...
TypeError: 'list' object is not callable
>>> priority_queue_test.array
[(5, 'A'), (10, 'B'), (15, 'C')]
>>> priority_queue_test.array = [(10, 'A'), (5, 'B'), (15, 'C')]
>>> priority_queue_test.min_heapify(0)
Traceback (most recent call last):
...
TypeError: 'list' object is not callable
>>> priority_queue_test.array
[(10, 'A'), (5, 'B'), (15, 'C')]
>>> priority_queue_test.array = [(10, 'A'), (15, 'B'), (5, 'C')]
>>> priority_queue_test.min_heapify(0)
Traceback (most recent call last):
...
TypeError: 'list' object is not callable
>>> priority_queue_test.array
[(10, 'A'), (15, 'B'), (5, 'C')]
>>> priority_queue_test.array = [(10, 'A'), (5, 'B')]
>>> priority_queue_test.cur_size = len(priority_queue_test.array)
>>> priority_queue_test.pos = {'A': 0, 'B': 1}
>>> priority_queue_test.min_heapify(0)
Traceback (most recent call last):
...
TypeError: 'list' object is not callable
>>> priority_queue_test.array
[(10, 'A'), (5, 'B')]
"""
lc = self.left(idx) lc = self.left(idx)
rc = self.right(idx) rc = self.right(idx)
if lc < self.cur_size and self.array(lc)[0] < self.array(idx)[0]: if lc < self.cur_size and self.array(lc)[0] < self.array[idx][0]:
smallest = lc smallest = lc
else: else:
smallest = idx smallest = idx
if rc < self.cur_size and self.array(rc)[0] < self.array(smallest)[0]: if rc < self.cur_size and self.array(rc)[0] < self.array[smallest][0]:
smallest = rc smallest = rc
if smallest != idx: if smallest != idx:
self.swap(idx, smallest) self.swap(idx, smallest)
self.min_heapify(smallest) self.min_heapify(smallest)
def insert(self, tup): def insert(self, tup):
# Inserts a node into the Priority Queue """
Inserts a node into the Priority Queue.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.insert((10, 'A'))
>>> priority_queue_test.array
[(10, 'A')]
>>> priority_queue_test.insert((15, 'B'))
>>> priority_queue_test.array
[(10, 'A'), (15, 'B')]
>>> priority_queue_test.insert((5, 'C'))
>>> priority_queue_test.array
[(5, 'C'), (10, 'A'), (15, 'B')]
"""
self.pos[tup[1]] = self.cur_size self.pos[tup[1]] = self.cur_size
self.cur_size += 1 self.cur_size += 1
self.array.append((sys.maxsize, tup[1])) self.array.append((sys.maxsize, tup[1]))
self.decrease_key((sys.maxsize, tup[1]), tup[0]) self.decrease_key((sys.maxsize, tup[1]), tup[0])
def extract_min(self): def extract_min(self):
# Removes and returns the min element at top of priority queue """
Removes and returns the min element at top of priority queue.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.array = [(10, 'A'), (15, 'B')]
>>> priority_queue_test.cur_size = len(priority_queue_test.array)
>>> priority_queue_test.pos = {'A': 0, 'B': 1}
>>> priority_queue_test.insert((5, 'C'))
>>> priority_queue_test.extract_min()
'C'
>>> priority_queue_test.array[0]
(15, 'B')
"""
min_node = self.array[0][1] min_node = self.array[0][1]
self.array[0] = self.array[self.cur_size - 1] self.array[0] = self.array[self.cur_size - 1]
self.cur_size -= 1 self.cur_size -= 1
@ -48,20 +140,61 @@ class PriorityQueue:
return min_node return min_node
def left(self, i): def left(self, i):
# returns the index of left child """
Returns the index of left child
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.left(0)
1
>>> priority_queue_test.left(1)
3
"""
return 2 * i + 1 return 2 * i + 1
def right(self, i): def right(self, i):
# returns the index of right child """
Returns the index of right child
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.right(0)
2
>>> priority_queue_test.right(1)
4
"""
return 2 * i + 2 return 2 * i + 2
def par(self, i): def par(self, i):
# returns the index of parent """
Returns the index of parent
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.par(1)
0
>>> priority_queue_test.par(2)
1
>>> priority_queue_test.par(4)
2
"""
return math.floor(i / 2) return math.floor(i / 2)
def swap(self, i, j): def swap(self, i, j):
# swaps array elements at indices i and j """
# update the pos{} Swaps array elements at indices i and j, update the pos{}
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.array = [(10, 'A'), (15, 'B')]
>>> priority_queue_test.cur_size = len(priority_queue_test.array)
>>> priority_queue_test.pos = {'A': 0, 'B': 1}
>>> priority_queue_test.swap(0, 1)
>>> priority_queue_test.array
[(15, 'B'), (10, 'A')]
>>> priority_queue_test.pos
{'A': 1, 'B': 0}
"""
self.pos[self.array[i][1]] = j self.pos[self.array[i][1]] = j
self.pos[self.array[j][1]] = i self.pos[self.array[j][1]] = i
temp = self.array[i] temp = self.array[i]
@ -69,6 +202,18 @@ class PriorityQueue:
self.array[j] = temp self.array[j] = temp
def decrease_key(self, tup, new_d): def decrease_key(self, tup, new_d):
"""
Decrease the key value for a given tuple, assuming the new_d is at most old_d.
Examples:
>>> priority_queue_test = PriorityQueue()
>>> priority_queue_test.array = [(10, 'A'), (15, 'B')]
>>> priority_queue_test.cur_size = len(priority_queue_test.array)
>>> priority_queue_test.pos = {'A': 0, 'B': 1}
>>> priority_queue_test.decrease_key((10, 'A'), 5)
>>> priority_queue_test.array
[(5, 'A'), (15, 'B')]
"""
idx = self.pos[tup[1]] idx = self.pos[tup[1]]
# assuming the new_d is atmost old_d # assuming the new_d is atmost old_d
self.array[idx] = (new_d, tup[1]) self.array[idx] = (new_d, tup[1])
@ -79,6 +224,20 @@ class PriorityQueue:
class Graph: class Graph:
def __init__(self, num): def __init__(self, num):
"""
Graph class constructor
Examples:
>>> graph_test = Graph(1)
>>> graph_test.num_nodes
1
>>> graph_test.dist
[0]
>>> graph_test.par
[-1]
>>> graph_test.adjList
{}
"""
self.adjList = {} # To store graph: u -> (v,w) self.adjList = {} # To store graph: u -> (v,w)
self.num_nodes = num # Number of nodes in graph self.num_nodes = num # Number of nodes in graph
# To store the distance from source vertex # To store the distance from source vertex
@ -86,8 +245,16 @@ class Graph:
self.par = [-1] * self.num_nodes # To store the path self.par = [-1] * self.num_nodes # To store the path
def add_edge(self, u, v, w): def add_edge(self, u, v, w):
# Edge going from node u to v and v to u with weight w """
# u (w)-> v, v (w) -> u Add edge going from node u to v and v to u with weight w: u (w)-> v, v (w) -> u
Examples:
>>> graph_test = Graph(1)
>>> graph_test.add_edge(1, 2, 1)
>>> graph_test.add_edge(2, 3, 2)
>>> graph_test.adjList
{1: [(2, 1)], 2: [(1, 1), (3, 2)], 3: [(2, 2)]}
"""
# Check if u already in graph # Check if u already in graph
if u in self.adjList: if u in self.adjList:
self.adjList[u].append((v, w)) self.adjList[u].append((v, w))
@ -101,11 +268,99 @@ class Graph:
self.adjList[v] = [(u, w)] self.adjList[v] = [(u, w)]
def show_graph(self): def show_graph(self):
# u -> v(w) """
Show the graph: u -> v(w)
Examples:
>>> graph_test = Graph(1)
>>> graph_test.add_edge(1, 2, 1)
>>> graph_test.show_graph()
1 -> 2(1)
2 -> 1(1)
>>> graph_test.add_edge(2, 3, 2)
>>> graph_test.show_graph()
1 -> 2(1)
2 -> 1(1) -> 3(2)
3 -> 2(2)
"""
for u in self.adjList: for u in self.adjList:
print(u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u])) print(u, "->", " -> ".join(str(f"{v}({w})") for v, w in self.adjList[u]))
def dijkstra(self, src): def dijkstra(self, src):
"""
Dijkstra algorithm
Examples:
>>> graph_test = Graph(3)
>>> graph_test.add_edge(0, 1, 2)
>>> graph_test.add_edge(1, 2, 2)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 2
Node 2 has distance: 4
>>> graph_test.dist
[0, 2, 4]
>>> graph_test = Graph(2)
>>> graph_test.add_edge(0, 1, 2)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 2
>>> graph_test.dist
[0, 2]
>>> graph_test = Graph(3)
>>> graph_test.add_edge(0, 1, 2)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 2
Node 2 has distance: 0
>>> graph_test.dist
[0, 2, 0]
>>> graph_test = Graph(3)
>>> graph_test.add_edge(0, 1, 2)
>>> graph_test.add_edge(1, 2, 2)
>>> graph_test.add_edge(0, 2, 1)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 2
Node 2 has distance: 1
>>> graph_test.dist
[0, 2, 1]
>>> graph_test = Graph(4)
>>> graph_test.add_edge(0, 1, 4)
>>> graph_test.add_edge(1, 2, 2)
>>> graph_test.add_edge(2, 3, 1)
>>> graph_test.add_edge(0, 2, 3)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 4
Node 2 has distance: 3
Node 3 has distance: 4
>>> graph_test.dist
[0, 4, 3, 4]
>>> graph_test = Graph(4)
>>> graph_test.add_edge(0, 1, 4)
>>> graph_test.add_edge(1, 2, 2)
>>> graph_test.add_edge(2, 3, 1)
>>> graph_test.add_edge(0, 2, 7)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 4
Node 2 has distance: 6
Node 3 has distance: 7
>>> graph_test.dist
[0, 4, 6, 7]
"""
# Flush old junk values in par[] # Flush old junk values in par[]
self.par = [-1] * self.num_nodes self.par = [-1] * self.num_nodes
# src is the source node # src is the source node
@ -135,13 +390,40 @@ class Graph:
self.show_distances(src) self.show_distances(src)
def show_distances(self, src): def show_distances(self, src):
"""
Show the distances from src to all other nodes in a graph
Examples:
>>> graph_test = Graph(1)
>>> graph_test.show_distances(0)
Distance from node: 0
Node 0 has distance: 0
"""
print(f"Distance from node: {src}") print(f"Distance from node: {src}")
for u in range(self.num_nodes): for u in range(self.num_nodes):
print(f"Node {u} has distance: {self.dist[u]}") print(f"Node {u} has distance: {self.dist[u]}")
def show_path(self, src, dest): def show_path(self, src, dest):
# To show the shortest path from src to dest """
# WARNING: Use it *after* calling dijkstra Shows the shortest path from src to dest.
WARNING: Use it *after* calling dijkstra.
Examples:
>>> graph_test = Graph(4)
>>> graph_test.add_edge(0, 1, 1)
>>> graph_test.add_edge(1, 2, 2)
>>> graph_test.add_edge(2, 3, 3)
>>> graph_test.dijkstra(0)
Distance from node: 0
Node 0 has distance: 0
Node 1 has distance: 1
Node 2 has distance: 3
Node 3 has distance: 6
>>> graph_test.show_path(0, 3) # doctest: +NORMALIZE_WHITESPACE
----Path to reach 3 from 0----
0 -> 1 -> 2 -> 3
Total cost of path: 6
"""
path = [] path = []
cost = 0 cost = 0
temp = dest temp = dest
@ -167,6 +449,9 @@ class Graph:
if __name__ == "__main__": if __name__ == "__main__":
from doctest import testmod
testmod()
graph = Graph(9) graph = Graph(9)
graph.add_edge(0, 1, 4) graph.add_edge(0, 1, 4)
graph.add_edge(0, 7, 8) graph.add_edge(0, 7, 8)