diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py new file mode 100644 index 000000000..a484339a9 --- /dev/null +++ b/pydatastructs/graphs/__init__.py @@ -0,0 +1,13 @@ +__all__ = [] + +from . import graph +from .graph import ( + Graph +) +__all__.extend(graph.__all__) + +from . import adjacency_list +from .adjacency_list import ( + AdjacencyList +) +__all__.extend(adjacency_list.__all__) diff --git a/pydatastructs/graphs/adjacency_list.py b/pydatastructs/graphs/adjacency_list.py new file mode 100644 index 000000000..9adc6a383 --- /dev/null +++ b/pydatastructs/graphs/adjacency_list.py @@ -0,0 +1,55 @@ +from pydatastructs.graphs.graph import Graph +from pydatastructs.linear_data_structures import DynamicOneDimensionalArray + +__all__ = [ + 'AdjacencyList' +] + +class AdjacencyList(Graph): + """ + Adjacency list implementation of graphs. + + See also + ======== + + pydatastructs.graphs.graph.Graph + """ + def __new__(cls, *vertices): + obj = object.__new__(cls) + for vertex in vertices: + obj.__setattr__(vertex.name, vertex) + obj.vertices = set([vertex.name for vertex in vertices]) + return obj + + def is_adjacent(self, node1, node2): + node1 = self.__getattribute__(node1) + return hasattr(node1, node2) + + def neighbors(self, node): + node = self.__getattribute__(node) + return [self.__getattribute__(name) for name in node.adjacent] + + def add_vertex(self, node): + self.vertices.add(node.name) + self.__setattr__(node.name, node) + + def remove_vertex(self, name): + delattr(self, name) + self.vertices.remove(name) + for node in self.vertices: + node_obj = self.__getattribute__(node) + if hasattr(node_obj, name): + delattr(node_obj, name) + node_obj.adjacent.remove(name) + + def add_edge(self, source, target): + source, target = self.__getattribute__(source), \ + self.__getattribute__(target) + source.__setattr__(target.name, target) + source.adjacent.add(target.name) + + def remove_edge(self, source, target): + source, target = self.__getattribute__(source), \ + self.__getattribute__(target) + source.adjacent.remove(target.name) + delattr(source, target.name) diff --git a/pydatastructs/graphs/graph.py b/pydatastructs/graphs/graph.py new file mode 100644 index 000000000..188ec3929 --- /dev/null +++ b/pydatastructs/graphs/graph.py @@ -0,0 +1,103 @@ + +__all__ = [ + 'Graph' +] + +class Graph(object): + """ + Represents generic concept of graphs. + + Parameters + ========== + + implementation: str + The implementation to be used for storing + graph in memory. + By default, 'adjacency_list' + vertices: AdjacencyListGraphNode(s) + For AdjacencyList implementation vertices + can be passed for initializing the graph. + + Examples + ======== + + >>> from pydatastructs.graphs import Graph + >>> from pydatastructs.utils import AdjacencyListGraphNode + >>> v_1 = AdjacencyListGraphNode('v_1', 1) + >>> v_2 = AdjacencyListGraphNode('v_2', 2) + >>> g = Graph(v_1, v_2) + >>> g.add_edge('v_1', 'v_2') + >>> g.add_edge('v_2', 'v_1') + >>> g.is_adjacent('v_1', 'v_2') + True + >>> g.is_adjacent('v_2', 'v_1') + True + >>> g.remove_edge('v_1', 'v_2') + >>> g.is_adjacent('v_1', 'v_2') + False + >>> g.is_adjacent('v_2', 'v_1') + True + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Graph_(abstract_data_type) + """ + 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) + else: + raise NotImplementedError("%s implementation is not a part " + "of the library currently."%(implementation)) + + def is_adjacent(self, node1, node2): + """ + Checks if the nodes with the given + with the given names are adjacent + to each other. + """ + raise NotImplementedError( + "This is an abstract method.") + + def neighbors(self, node): + """ + Lists the neighbors of the node + with given name. + """ + raise NotImplementedError( + "This is an abstract method.") + + def add_vertex(self, node): + """ + Adds the input vertex to the node. + """ + raise NotImplementedError( + "This is an abstract method.") + + def remove_vertex(self, node): + """ + Removes the input vertex along with all the edges + pointing towards to it. + """ + raise NotImplementedError( + "This is an abstract method.") + + def add_edge(self, source, target): + """ + Adds the edge starting at first parameter + i.e., source and ending at the second + parameter i.e., target. + """ + raise NotImplementedError( + "This is an abstract method.") + + def remove_edge(self, source, target): + """ + Removes the edge starting at first parameter + i.e., source and ending at the second + parameter i.e., target. + """ + raise NotImplementedError( + "This is an abstract method.") diff --git a/pydatastructs/graphs/tests/__init__.py b/pydatastructs/graphs/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/pydatastructs/graphs/tests/test_adjacency_list.py b/pydatastructs/graphs/tests/test_adjacency_list.py new file mode 100644 index 000000000..5581e1430 --- /dev/null +++ b/pydatastructs/graphs/tests/test_adjacency_list.py @@ -0,0 +1,34 @@ +from pydatastructs.graphs import Graph +from pydatastructs.utils import AdjacencyListGraphNode + +def test_adjacency_list(): + v_1 = AdjacencyListGraphNode('v_1', 1) + v_2 = AdjacencyListGraphNode('v_2', 2) + g = Graph(v_1, v_2, implementation='adjacency_list') + v_3 = AdjacencyListGraphNode('v_3', 3) + g.add_vertex(v_2) + g.add_vertex(v_3) + g.add_edge('v_1', 'v_2') + g.add_edge('v_2', 'v_3') + g.add_edge('v_3', 'v_1') + assert g.is_adjacent('v_1', 'v_2') is True + assert g.is_adjacent('v_2', 'v_3') is True + assert g.is_adjacent('v_3', 'v_1') is True + assert g.is_adjacent('v_2', 'v_1') is False + assert g.is_adjacent('v_3', 'v_2') is False + assert g.is_adjacent('v_1', 'v_3') is False + neighbors = g.neighbors('v_1') + assert neighbors == [v_2] + v = AdjacencyListGraphNode('v', 4) + g.add_vertex(v) + g.add_edge('v_1', 'v') + g.add_edge('v_2', 'v') + g.add_edge('v_3', 'v') + assert g.is_adjacent('v_1', 'v') is True + assert g.is_adjacent('v_2', 'v') is True + assert g.is_adjacent('v_3', 'v') is True + g.remove_edge('v_1', 'v') + assert g.is_adjacent('v_1', 'v') is False + g.remove_vertex('v') + assert g.is_adjacent('v_2', 'v') is False + assert g.is_adjacent('v_3', 'v') is False diff --git a/pydatastructs/utils/__init__.py b/pydatastructs/utils/__init__.py index 37e2067b9..bb63db83e 100644 --- a/pydatastructs/utils/__init__.py +++ b/pydatastructs/utils/__init__.py @@ -4,6 +4,9 @@ from .misc_util import ( TreeNode, LinkedListNode, - BinomialTreeNode + BinomialTreeNode, + AdjacencyListGraphNode, + AdjacencyMatrixGraphNode, + GraphEdge ) __all__.extend(misc_util.__all__) diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index d13182f59..419d7d098 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -1,7 +1,10 @@ __all__ = [ 'TreeNode', 'LinkedListNode', - 'BinomialTreeNode' + 'BinomialTreeNode', + 'AdjacencyListGraphNode', + 'AdjacencyMatrixGraphNode', + 'GraphEdge' ] _check_type = lambda a, t: isinstance(a, t) @@ -34,7 +37,7 @@ class TreeNode(Node): 'height', 'parent', 'size'] def __new__(cls, key, data): - obj = object.__new__(cls) + obj = Node.__new__(cls) obj.data, obj.key = data, key obj.left, obj.right, obj.parent, obj.height, obj.size = \ None, None, None, 0, 1 @@ -78,7 +81,7 @@ class BinomialTreeNode(TreeNode): def __new__(cls, key, data): from pydatastructs.linear_data_structures.arrays import DynamicOneDimensionalArray - obj = object.__new__(cls) + obj = Node.__new__(cls) obj.data, obj.key = data, key obj.children, obj.parent, obj.is_root = ( DynamicOneDimensionalArray(BinomialTreeNode, 0), @@ -112,7 +115,7 @@ class LinkedListNode(Node): Any valid data to be stored in the node. """ def __new__(cls, data=None, links=['next'], addrs=[None]): - obj = object.__new__(cls) + obj = Node.__new__(cls) obj.data = data for link, addr in zip(links, addrs): obj.__setattr__(link, addr) @@ -121,3 +124,102 @@ def __new__(cls, data=None, links=['next'], addrs=[None]): def __str__(self): return str(self.data) + +class GraphNode(Node): + """ + Abastract class for graph nodes/vertices. + """ + def __str__(self): + return str((self.name, self.data)) + +class AdjacencyListGraphNode(GraphNode): + """ + Represents nodes for adjacency list implementation + of graphs. + + Parameters + ========== + + name: str + The name of the node by which it is identified + in the graph. Must be unique. + data + The data to be stored at each graph node. + adjacency_list: iterator + Any valid iterator to initialize the adjacent + nodes of the current node. + Optional, by default, None + """ + def __new__(cls, name, data, adjacency_list=None): + obj = GraphNode.__new__(cls) + obj.name, obj.data = name, data + if adjacency_list is not None: + for node in adjacency_list: + obj.__setattr__(node.name, node) + obj.adjacent = set(adjacency_list) if adjacency_list is not None \ + else set() + return obj + + def add_adjacent_node(self, name, data): + """ + Adds adjacent node to the current node's + adjacency list with given name and data. + """ + if hasattr(self, name): + getattr(self, name).data = data + else: + new_node = AdjacencyListGraphNode(name, data) + self.__setattr__(new_node.name, new_node) + self.adjacent.add(new_node.name) + + def remove_adjacent_node(self, name): + """ + Removes node with given name from + adjacency list. + """ + if not hasattr(self, name): + raise ValueError("%s is not adjacent to %s"%(name, self.name)) + self.adjacent.remove(name) + delattr(self, name) + +class AdjacencyMatrixGraphNode(GraphNode): + """ + Represents nodes for adjacency matrix implementation + of graphs. + + Parameters + ========== + + name: str + The name of the node by which it is identified + in the graph. Must be unique. + data + The data to be stored at each graph node. + """ + __slots__ = ['name', 'data'] + + def __new__(cls, name, data): + obj = GraphNode.__new__(cls) + obj.name, obj.data = name, data + return obj + +class GraphEdge(object): + """ + Represents the concept of edges in graphs. + + Parameters + ========== + + node1: GraphNode or it's child classes + The source node of the edge. + node2: GraphNode or it's child classes + The target node of the edge. + """ + def __new__(cls, node1, node2, value=None): + obj = object.__new__(cls) + obj.source, obj.target = node1, node2 + obj.value = value + return obj + + def __str__(self): + return str((self.source.name, self.target.name)) diff --git a/pydatastructs/utils/tests/test_misc_util.py b/pydatastructs/utils/tests/test_misc_util.py new file mode 100644 index 000000000..f699ed748 --- /dev/null +++ b/pydatastructs/utils/tests/test_misc_util.py @@ -0,0 +1,27 @@ +from pydatastructs.utils import AdjacencyListGraphNode, AdjacencyMatrixGraphNode, GraphEdge +from pydatastructs.utils.raises_util import raises + +def test_AdjacencyListGraphNode(): + g_1 = AdjacencyListGraphNode('g_1', 1) + g_2 = AdjacencyListGraphNode('g_2', 2) + g = AdjacencyListGraphNode('g', 0, adjacency_list=[g_1, g_2]) + g.add_adjacent_node('g_3', 3) + assert g.g_1.name is 'g_1' + assert g.g_2.name is 'g_2' + assert g.g_3.name is 'g_3' + g.remove_adjacent_node('g_3') + assert hasattr(g, 'g_3') is False + assert raises(ValueError, lambda: g.remove_adjacent_node('g_3')) + g.add_adjacent_node('g_1', 4) + assert g.g_1.data is 4 + assert str(g) == "('g', 0)" + +def test_AdjacencyMatrixGraphNode(): + g = AdjacencyMatrixGraphNode('g', 3) + assert str(g) == "('g', 3)" + +def test_GraphEdge(): + g_1 = AdjacencyListGraphNode('g_1', 1) + g_2 = AdjacencyListGraphNode('g_2', 2) + e = GraphEdge(g_1, g_2, value=2) + assert str(e) == "('g_1', 'g_2')"