Skip to content

Commit 8c322a4

Browse files
authored
Fixed kahn's topological sort and added parallel topological sort (#288)
1 parent e625a9f commit 8c322a4

File tree

3 files changed

+102
-13
lines changed

3 files changed

+102
-13
lines changed

pydatastructs/graphs/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
strongly_connected_components,
1616
depth_first_search,
1717
shortest_paths,
18-
topological_sort
18+
topological_sort,
19+
topological_sort_parallel
1920
)
2021

2122
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
'strongly_connected_components',
2020
'depth_first_search',
2121
'shortest_paths',
22-
'topological_sort'
22+
'topological_sort',
23+
'topological_sort_parallel'
2324
]
2425

2526
Stack = Queue = deque
@@ -772,27 +773,107 @@ def topological_sort(graph: Graph, algorithm: str) -> list:
772773
return getattr(algorithms, func)(graph)
773774

774775
def _kahn_adjacency_list(graph: Graph) -> list:
775-
S = set(graph.vertices)
776-
in_degree = dict()
776+
S = Queue()
777+
in_degree = {u: 0 for u in graph.vertices}
777778
for u in graph.vertices:
778779
for v in graph.neighbors(u):
779-
if v.name not in in_degree:
780-
in_degree[v.name] = 0
781780
in_degree[v.name] += 1
782-
if v.name in S:
783-
S.remove(v.name)
781+
for u in graph.vertices:
782+
if in_degree[u] == 0:
783+
S.append(u)
784+
in_degree.pop(u)
784785

785786
L = []
786787
while S:
787-
n = S.pop()
788+
n = S.popleft()
788789
L.append(n)
789790
for m in graph.neighbors(n):
790791
graph.remove_edge(n, m.name)
791792
in_degree[m.name] -= 1
792793
if in_degree[m.name] == 0:
793-
S.add(m.name)
794+
S.append(m.name)
794795
in_degree.pop(m.name)
795796

796797
if in_degree:
797798
raise ValueError("Graph is not acyclic.")
798799
return L
800+
801+
def topological_sort_parallel(graph: Graph, algorithm: str, num_threads: int) -> list:
802+
"""
803+
Performs topological sort on the given graph using given algorithm using
804+
given number of threads.
805+
806+
Parameters
807+
==========
808+
809+
graph: Graph
810+
The graph under consideration.
811+
algorithm: str
812+
The algorithm to be used.
813+
Currently, following are supported,
814+
'kahn' -> Kahn's algorithm as given in [1].
815+
num_threads: int
816+
The maximum number of threads to be used.
817+
818+
Returns
819+
=======
820+
821+
list
822+
The list of topologically sorted vertices.
823+
824+
Examples
825+
========
826+
827+
>>> from pydatastructs import Graph, AdjacencyListGraphNode, topological_sort_parallel
828+
>>> v_1 = AdjacencyListGraphNode('v_1')
829+
>>> v_2 = AdjacencyListGraphNode('v_2')
830+
>>> graph = Graph(v_1, v_2)
831+
>>> graph.add_edge('v_1', 'v_2')
832+
>>> topological_sort_parallel(graph, 'kahn', 1)
833+
['v_1', 'v_2']
834+
835+
References
836+
==========
837+
838+
.. [1] https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
839+
"""
840+
import pydatastructs.graphs.algorithms as algorithms
841+
func = "_" + algorithm + "_" + graph._impl + '_parallel'
842+
if not hasattr(algorithms, func):
843+
raise NotImplementedError(
844+
"Currently %s algorithm isn't implemented for "
845+
"performing topological sort on %s graphs."%(algorithm, graph._impl))
846+
return getattr(algorithms, func)(graph, num_threads)
847+
848+
def _kahn_adjacency_list_parallel(graph: Graph, num_threads: int) -> list:
849+
num_vertices = len(graph.vertices)
850+
851+
def _collect_source_nodes(graph: Graph) -> list:
852+
S = []
853+
in_degree = {u: 0 for u in graph.vertices}
854+
for u in graph.vertices:
855+
for v in graph.neighbors(u):
856+
in_degree[v.name] += 1
857+
for u in in_degree:
858+
if in_degree[u] == 0:
859+
S.append(u)
860+
return list(S)
861+
862+
def _job(graph: Graph, u: str):
863+
for v in graph.neighbors(u):
864+
graph.remove_edge(u, v.name)
865+
866+
L = []
867+
source_nodes = _collect_source_nodes(graph)
868+
while source_nodes:
869+
with ThreadPoolExecutor(max_workers=num_threads) as Executor:
870+
for node in source_nodes:
871+
L.append(node)
872+
Executor.submit(_job, graph, node)
873+
for node in source_nodes:
874+
graph.remove_vertex(node)
875+
source_nodes = _collect_source_nodes(graph)
876+
877+
if len(L) != num_vertices:
878+
raise ValueError("Graph is not acyclic.")
879+
return L

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from pydatastructs import (breadth_first_search, Graph,
22
breadth_first_search_parallel, minimum_spanning_tree,
33
minimum_spanning_tree_parallel, strongly_connected_components,
4-
depth_first_search, shortest_paths, topological_sort)
4+
depth_first_search, shortest_paths, topological_sort,
5+
topological_sort_parallel)
56
from pydatastructs.utils.raises_util import raises
67

78
def test_breadth_first_search():
@@ -292,7 +293,7 @@ def _test_shortest_paths(ds, algorithm):
292293

293294
def test_topological_sort():
294295

295-
def _test_topological_sort(ds, algorithm):
296+
def _test_topological_sort(func, ds, algorithm, threads=None):
296297
import pydatastructs.utils.misc_util as utils
297298
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
298299
vertices = [GraphNode('2'), GraphNode('3'), GraphNode('5'),
@@ -309,7 +310,13 @@ def _test_topological_sort(ds, algorithm):
309310
graph.add_edge('11', '9')
310311
graph.add_edge('11', '10')
311312
graph.add_edge('8', '9')
312-
l = topological_sort(graph, algorithm)
313+
if threads is not None:
314+
l = func(graph, algorithm, threads)
315+
else:
316+
l = func(graph, algorithm)
313317
assert all([(l1 in l[0:3]) for l1 in ('3', '5', '7')] +
314318
[(l2 in l[3:5]) for l2 in ('8', '11')] +
315319
[(l3 in l[5:]) for l3 in ('10', '9', '2')])
320+
321+
_test_topological_sort(topological_sort, "List", "kahn")
322+
_test_topological_sort(topological_sort_parallel, "List", "kahn", 3)

0 commit comments

Comments
 (0)