diff --git a/pydatastructs/__init__.py b/pydatastructs/__init__.py index 40471c3f3..ca6c0c75a 100644 --- a/pydatastructs/__init__.py +++ b/pydatastructs/__init__.py @@ -2,3 +2,4 @@ from .trees import * from .miscellaneous_data_structures import * from .utils import * +from .graphs import * diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 423345946..8fd88ea7b 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -5,3 +5,10 @@ Graph ) __all__.extend(graph.__all__) + +from . import algorithms +from .algorithms import ( + breadth_first_search +) + +__all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py new file mode 100644 index 000000000..8a0bbd320 --- /dev/null +++ b/pydatastructs/graphs/algorithms.py @@ -0,0 +1,92 @@ +""" +Contains all the algorithms associated with graph +data structure. +""" +# TODO: REPLACE COLLECTIONS QUEUE WITH PYDATASTRUCTS QUEUE +from collections import deque as Queue +from pydatastructs.utils.misc_util import AdjacencyListGraphNode + +__all__ = [ + 'breadth_first_search', +] + +def breadth_first_search( + graph, source_node, operation, *args, **kwargs): + """ + Implementation of serial breadth first search(BFS) + algorithm. + + Parameters + ========== + + graph: Graph + The graph on which BFS is to be performed. + source_node: str + The name of the source node from where the BFS is + to be initiated. + operation: function + The function which is to be applied + on every node when it is visited. + The prototype which is to be followed is, + `function_name(curr_node, next_node, + arg_1, arg_2, . . ., arg_n)`. + Here, the first two arguments denote, the + current node and the node next to current node. + The rest of the arguments are optional and you can + provide your own stuff there. + + Note + ==== + + You should pass all the arguments which you are going + to use in the prototype of your `operation` after + passing the operation function. + + Examples + ======== + + >>> from pydatastructs import Graph, AdjacencyListGraphNode + >>> V1 = AdjacencyListGraphNode("V1") + >>> V2 = AdjacencyListGraphNode("V2") + >>> V3 = AdjacencyListGraphNode("V3") + >>> G = Graph(V1, V2, V3) + >>> from pydatastructs import breadth_first_search + >>> def f(curr_node, next_node, dest_node): + ... return curr_node != dest_node + ... + >>> G.add_edge(V1.name, V2.name) + >>> G.add_edge(V2.name, V3.name) + >>> breadth_first_search(G, V1.name, f, V3.name) + """ + import pydatastructs.graphs.algorithms as algorithms + func = "_breadth_first_search_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently breadth first search isn't implemented for " + "%s graphs."%(graph._impl)) + return getattr(algorithms, func)( + graph, source_node, operation, *args, **kwargs) + +def _breadth_first_search_adjacency_list( + graph, source_node, operation, *args, **kwargs): + bfs_queue = Queue() + visited = dict() + bfs_queue.append(source_node) + visited[source_node] = True + while len(bfs_queue) != 0: + curr_node = bfs_queue.popleft() + next_nodes = graph.neighbors(curr_node) + if len(next_nodes) != 0: + for next_node in next_nodes: + if visited.get(next_node.name, False) is False: + status = operation(curr_node, next_node.name, *args, **kwargs) + if not status: + return None + bfs_queue.append(next_node.name) + visited[next_node.name] = True + else: + status = operation(curr_node, "", *args, **kwargs) + if not status: + return None + +_breadth_first_search_adjacency_matrix = _breadth_first_search_adjacency_list diff --git a/pydatastructs/graphs/graph.py b/pydatastructs/graphs/graph.py index b536f7155..c532e8882 100644 --- a/pydatastructs/graphs/graph.py +++ b/pydatastructs/graphs/graph.py @@ -43,14 +43,21 @@ class Graph(object): .. [1] https://en.wikipedia.org/wiki/Graph_(abstract_data_type) """ + + __slots__ = ['_impl'] + def __new__(cls, *args, **kwargs): implementation = kwargs.get('implementation', 'adjacency_list') if implementation is 'adjacency_list': from pydatastructs.graphs.adjacency_list import AdjacencyList - return AdjacencyList(*args) + obj = AdjacencyList(*args) + obj._impl = implementation + return obj elif implementation is 'adjacency_matrix': from pydatastructs.graphs.adjacency_matrix import AdjacencyMatrix - return AdjacencyMatrix(*args) + obj = AdjacencyMatrix(*args) + obj._impl = implementation + return obj else: raise NotImplementedError("%s implementation is not a part " "of the library currently."%(implementation)) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py new file mode 100644 index 000000000..208108120 --- /dev/null +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -0,0 +1,74 @@ +from pydatastructs import breadth_first_search, Graph + + +def test_breadth_first_search(): + + def _test_breadth_first_search(ds, impl): + import pydatastructs.utils.misc_util as utils + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + + V1 = GraphNode(0) + V2 = GraphNode(1) + V3 = GraphNode(2) + + G1 = Graph(V1, V2, V3, implementation=impl) + + edges = [ + (V1.name, V2.name), + (V2.name, V3.name), + (V1.name, V3.name) + ] + + for edge in edges: + G1.add_edge(*edge) + + parent = dict() + def bfs_tree(curr_node, next_node, parent): + if next_node != "": + parent[next_node] = curr_node + return True + + breadth_first_search(G1, V1.name, bfs_tree, parent) + assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \ + (parent[V3.name] == V2.name and parent[V2.name] == V1.name) + + V4 = GraphNode(0) + V5 = GraphNode(1) + V6 = GraphNode(2) + V7 = GraphNode(3) + V8 = GraphNode(4) + + edges = [ + (V4.name, V5.name), + (V5.name, V6.name), + (V6.name, V7.name), + (V6.name, V4.name), + (V7.name, V8.name) + ] + + G2 = Graph(V4, V5, V6, V7, V8, implementation=impl) + + for edge in edges: + G2.add_edge(*edge) + + path = [] + def path_finder(curr_node, next_node, dest_node, parent, path): + if next_node != "": + parent[next_node] = curr_node + if curr_node == dest_node: + node = curr_node + path.append(node) + while node is not None: + if parent.get(node, None) is not None: + path.append(parent[node]) + node = parent.get(node, None) + path.reverse() + return False + return True + + parent.clear() + breadth_first_search(G2, V4.name, path_finder, V7.name, parent, path) + assert path == [V4.name, V5.name, V6.name, V7.name] + + _test_breadth_first_search("List", "adjacency_list") + _test_breadth_first_search("Matrix", "adjacency_matrix") diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index 5db27a374..4f041b4ef 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -150,9 +150,9 @@ class AdjacencyListGraphNode(GraphNode): nodes of the current node. Optional, by default, None """ - def __new__(cls, name, data, adjacency_list=None): + def __new__(cls, name, data=None, adjacency_list=None): obj = GraphNode.__new__(cls) - obj.name, obj.data = name, data + obj.name, obj.data = str(name), data if adjacency_list is not None: for node in adjacency_list: obj.__setattr__(node.name, node) @@ -197,10 +197,10 @@ class AdjacencyMatrixGraphNode(GraphNode): """ __slots__ = ['name', 'data'] - def __new__(cls, name, data): + def __new__(cls, name, data=None): obj = GraphNode.__new__(cls) obj.name, obj.data, obj.is_connected = \ - name, data, None + int(name), data, None return obj class GraphEdge(object):