Skip to content

Commit 89e277f

Browse files
authored
Added Graphs (#80)
The following classes have been added * pydatastructs.graphs.graph.Graph * pydatastructs.graphs.adjacency_list.AdjacencyList * pydatastructs.utils.misc_util.GraphNode * pydatastructs.utils.misc_util.AdjacencyListGraphNode * pydatastructs.utils.misc_util.AdjacencyMatrixGraphNode
1 parent 9ee423c commit 89e277f

File tree

8 files changed

+342
-5
lines changed

8 files changed

+342
-5
lines changed

pydatastructs/graphs/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
__all__ = []
2+
3+
from . import graph
4+
from .graph import (
5+
Graph
6+
)
7+
__all__.extend(graph.__all__)
8+
9+
from . import adjacency_list
10+
from .adjacency_list import (
11+
AdjacencyList
12+
)
13+
__all__.extend(adjacency_list.__all__)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from pydatastructs.graphs.graph import Graph
2+
from pydatastructs.linear_data_structures import DynamicOneDimensionalArray
3+
4+
__all__ = [
5+
'AdjacencyList'
6+
]
7+
8+
class AdjacencyList(Graph):
9+
"""
10+
Adjacency list implementation of graphs.
11+
12+
See also
13+
========
14+
15+
pydatastructs.graphs.graph.Graph
16+
"""
17+
def __new__(cls, *vertices):
18+
obj = object.__new__(cls)
19+
for vertex in vertices:
20+
obj.__setattr__(vertex.name, vertex)
21+
obj.vertices = set([vertex.name for vertex in vertices])
22+
return obj
23+
24+
def is_adjacent(self, node1, node2):
25+
node1 = self.__getattribute__(node1)
26+
return hasattr(node1, node2)
27+
28+
def neighbors(self, node):
29+
node = self.__getattribute__(node)
30+
return [self.__getattribute__(name) for name in node.adjacent]
31+
32+
def add_vertex(self, node):
33+
self.vertices.add(node.name)
34+
self.__setattr__(node.name, node)
35+
36+
def remove_vertex(self, name):
37+
delattr(self, name)
38+
self.vertices.remove(name)
39+
for node in self.vertices:
40+
node_obj = self.__getattribute__(node)
41+
if hasattr(node_obj, name):
42+
delattr(node_obj, name)
43+
node_obj.adjacent.remove(name)
44+
45+
def add_edge(self, source, target):
46+
source, target = self.__getattribute__(source), \
47+
self.__getattribute__(target)
48+
source.__setattr__(target.name, target)
49+
source.adjacent.add(target.name)
50+
51+
def remove_edge(self, source, target):
52+
source, target = self.__getattribute__(source), \
53+
self.__getattribute__(target)
54+
source.adjacent.remove(target.name)
55+
delattr(source, target.name)

pydatastructs/graphs/graph.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
2+
__all__ = [
3+
'Graph'
4+
]
5+
6+
class Graph(object):
7+
"""
8+
Represents generic concept of graphs.
9+
10+
Parameters
11+
==========
12+
13+
implementation: str
14+
The implementation to be used for storing
15+
graph in memory.
16+
By default, 'adjacency_list'
17+
vertices: AdjacencyListGraphNode(s)
18+
For AdjacencyList implementation vertices
19+
can be passed for initializing the graph.
20+
21+
Examples
22+
========
23+
24+
>>> from pydatastructs.graphs import Graph
25+
>>> from pydatastructs.utils import AdjacencyListGraphNode
26+
>>> v_1 = AdjacencyListGraphNode('v_1', 1)
27+
>>> v_2 = AdjacencyListGraphNode('v_2', 2)
28+
>>> g = Graph(v_1, v_2)
29+
>>> g.add_edge('v_1', 'v_2')
30+
>>> g.add_edge('v_2', 'v_1')
31+
>>> g.is_adjacent('v_1', 'v_2')
32+
True
33+
>>> g.is_adjacent('v_2', 'v_1')
34+
True
35+
>>> g.remove_edge('v_1', 'v_2')
36+
>>> g.is_adjacent('v_1', 'v_2')
37+
False
38+
>>> g.is_adjacent('v_2', 'v_1')
39+
True
40+
41+
References
42+
==========
43+
44+
.. [1] https://en.wikipedia.org/wiki/Graph_(abstract_data_type)
45+
"""
46+
def __new__(cls, *args, **kwargs):
47+
implementation = kwargs.get('implementation', 'adjacency_list')
48+
if implementation is 'adjacency_list':
49+
from pydatastructs.graphs.adjacency_list import AdjacencyList
50+
return AdjacencyList(*args)
51+
else:
52+
raise NotImplementedError("%s implementation is not a part "
53+
"of the library currently."%(implementation))
54+
55+
def is_adjacent(self, node1, node2):
56+
"""
57+
Checks if the nodes with the given
58+
with the given names are adjacent
59+
to each other.
60+
"""
61+
raise NotImplementedError(
62+
"This is an abstract method.")
63+
64+
def neighbors(self, node):
65+
"""
66+
Lists the neighbors of the node
67+
with given name.
68+
"""
69+
raise NotImplementedError(
70+
"This is an abstract method.")
71+
72+
def add_vertex(self, node):
73+
"""
74+
Adds the input vertex to the node.
75+
"""
76+
raise NotImplementedError(
77+
"This is an abstract method.")
78+
79+
def remove_vertex(self, node):
80+
"""
81+
Removes the input vertex along with all the edges
82+
pointing towards to it.
83+
"""
84+
raise NotImplementedError(
85+
"This is an abstract method.")
86+
87+
def add_edge(self, source, target):
88+
"""
89+
Adds the edge starting at first parameter
90+
i.e., source and ending at the second
91+
parameter i.e., target.
92+
"""
93+
raise NotImplementedError(
94+
"This is an abstract method.")
95+
96+
def remove_edge(self, source, target):
97+
"""
98+
Removes the edge starting at first parameter
99+
i.e., source and ending at the second
100+
parameter i.e., target.
101+
"""
102+
raise NotImplementedError(
103+
"This is an abstract method.")

pydatastructs/graphs/tests/__init__.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from pydatastructs.graphs import Graph
2+
from pydatastructs.utils import AdjacencyListGraphNode
3+
4+
def test_adjacency_list():
5+
v_1 = AdjacencyListGraphNode('v_1', 1)
6+
v_2 = AdjacencyListGraphNode('v_2', 2)
7+
g = Graph(v_1, v_2, implementation='adjacency_list')
8+
v_3 = AdjacencyListGraphNode('v_3', 3)
9+
g.add_vertex(v_2)
10+
g.add_vertex(v_3)
11+
g.add_edge('v_1', 'v_2')
12+
g.add_edge('v_2', 'v_3')
13+
g.add_edge('v_3', 'v_1')
14+
assert g.is_adjacent('v_1', 'v_2') is True
15+
assert g.is_adjacent('v_2', 'v_3') is True
16+
assert g.is_adjacent('v_3', 'v_1') is True
17+
assert g.is_adjacent('v_2', 'v_1') is False
18+
assert g.is_adjacent('v_3', 'v_2') is False
19+
assert g.is_adjacent('v_1', 'v_3') is False
20+
neighbors = g.neighbors('v_1')
21+
assert neighbors == [v_2]
22+
v = AdjacencyListGraphNode('v', 4)
23+
g.add_vertex(v)
24+
g.add_edge('v_1', 'v')
25+
g.add_edge('v_2', 'v')
26+
g.add_edge('v_3', 'v')
27+
assert g.is_adjacent('v_1', 'v') is True
28+
assert g.is_adjacent('v_2', 'v') is True
29+
assert g.is_adjacent('v_3', 'v') is True
30+
g.remove_edge('v_1', 'v')
31+
assert g.is_adjacent('v_1', 'v') is False
32+
g.remove_vertex('v')
33+
assert g.is_adjacent('v_2', 'v') is False
34+
assert g.is_adjacent('v_3', 'v') is False

pydatastructs/utils/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
from .misc_util import (
55
TreeNode,
66
LinkedListNode,
7-
BinomialTreeNode
7+
BinomialTreeNode,
8+
AdjacencyListGraphNode,
9+
AdjacencyMatrixGraphNode,
10+
GraphEdge
811
)
912
__all__.extend(misc_util.__all__)

pydatastructs/utils/misc_util.py

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
__all__ = [
22
'TreeNode',
33
'LinkedListNode',
4-
'BinomialTreeNode'
4+
'BinomialTreeNode',
5+
'AdjacencyListGraphNode',
6+
'AdjacencyMatrixGraphNode',
7+
'GraphEdge'
58
]
69

710
_check_type = lambda a, t: isinstance(a, t)
@@ -34,7 +37,7 @@ class TreeNode(Node):
3437
'height', 'parent', 'size']
3538

3639
def __new__(cls, key, data):
37-
obj = object.__new__(cls)
40+
obj = Node.__new__(cls)
3841
obj.data, obj.key = data, key
3942
obj.left, obj.right, obj.parent, obj.height, obj.size = \
4043
None, None, None, 0, 1
@@ -78,7 +81,7 @@ class BinomialTreeNode(TreeNode):
7881

7982
def __new__(cls, key, data):
8083
from pydatastructs.linear_data_structures.arrays import DynamicOneDimensionalArray
81-
obj = object.__new__(cls)
84+
obj = Node.__new__(cls)
8285
obj.data, obj.key = data, key
8386
obj.children, obj.parent, obj.is_root = (
8487
DynamicOneDimensionalArray(BinomialTreeNode, 0),
@@ -112,7 +115,7 @@ class LinkedListNode(Node):
112115
Any valid data to be stored in the node.
113116
"""
114117
def __new__(cls, data=None, links=['next'], addrs=[None]):
115-
obj = object.__new__(cls)
118+
obj = Node.__new__(cls)
116119
obj.data = data
117120
for link, addr in zip(links, addrs):
118121
obj.__setattr__(link, addr)
@@ -121,3 +124,102 @@ def __new__(cls, data=None, links=['next'], addrs=[None]):
121124

122125
def __str__(self):
123126
return str(self.data)
127+
128+
class GraphNode(Node):
129+
"""
130+
Abastract class for graph nodes/vertices.
131+
"""
132+
def __str__(self):
133+
return str((self.name, self.data))
134+
135+
class AdjacencyListGraphNode(GraphNode):
136+
"""
137+
Represents nodes for adjacency list implementation
138+
of graphs.
139+
140+
Parameters
141+
==========
142+
143+
name: str
144+
The name of the node by which it is identified
145+
in the graph. Must be unique.
146+
data
147+
The data to be stored at each graph node.
148+
adjacency_list: iterator
149+
Any valid iterator to initialize the adjacent
150+
nodes of the current node.
151+
Optional, by default, None
152+
"""
153+
def __new__(cls, name, data, adjacency_list=None):
154+
obj = GraphNode.__new__(cls)
155+
obj.name, obj.data = name, data
156+
if adjacency_list is not None:
157+
for node in adjacency_list:
158+
obj.__setattr__(node.name, node)
159+
obj.adjacent = set(adjacency_list) if adjacency_list is not None \
160+
else set()
161+
return obj
162+
163+
def add_adjacent_node(self, name, data):
164+
"""
165+
Adds adjacent node to the current node's
166+
adjacency list with given name and data.
167+
"""
168+
if hasattr(self, name):
169+
getattr(self, name).data = data
170+
else:
171+
new_node = AdjacencyListGraphNode(name, data)
172+
self.__setattr__(new_node.name, new_node)
173+
self.adjacent.add(new_node.name)
174+
175+
def remove_adjacent_node(self, name):
176+
"""
177+
Removes node with given name from
178+
adjacency list.
179+
"""
180+
if not hasattr(self, name):
181+
raise ValueError("%s is not adjacent to %s"%(name, self.name))
182+
self.adjacent.remove(name)
183+
delattr(self, name)
184+
185+
class AdjacencyMatrixGraphNode(GraphNode):
186+
"""
187+
Represents nodes for adjacency matrix implementation
188+
of graphs.
189+
190+
Parameters
191+
==========
192+
193+
name: str
194+
The name of the node by which it is identified
195+
in the graph. Must be unique.
196+
data
197+
The data to be stored at each graph node.
198+
"""
199+
__slots__ = ['name', 'data']
200+
201+
def __new__(cls, name, data):
202+
obj = GraphNode.__new__(cls)
203+
obj.name, obj.data = name, data
204+
return obj
205+
206+
class GraphEdge(object):
207+
"""
208+
Represents the concept of edges in graphs.
209+
210+
Parameters
211+
==========
212+
213+
node1: GraphNode or it's child classes
214+
The source node of the edge.
215+
node2: GraphNode or it's child classes
216+
The target node of the edge.
217+
"""
218+
def __new__(cls, node1, node2, value=None):
219+
obj = object.__new__(cls)
220+
obj.source, obj.target = node1, node2
221+
obj.value = value
222+
return obj
223+
224+
def __str__(self):
225+
return str((self.source.name, self.target.name))
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from pydatastructs.utils import AdjacencyListGraphNode, AdjacencyMatrixGraphNode, GraphEdge
2+
from pydatastructs.utils.raises_util import raises
3+
4+
def test_AdjacencyListGraphNode():
5+
g_1 = AdjacencyListGraphNode('g_1', 1)
6+
g_2 = AdjacencyListGraphNode('g_2', 2)
7+
g = AdjacencyListGraphNode('g', 0, adjacency_list=[g_1, g_2])
8+
g.add_adjacent_node('g_3', 3)
9+
assert g.g_1.name is 'g_1'
10+
assert g.g_2.name is 'g_2'
11+
assert g.g_3.name is 'g_3'
12+
g.remove_adjacent_node('g_3')
13+
assert hasattr(g, 'g_3') is False
14+
assert raises(ValueError, lambda: g.remove_adjacent_node('g_3'))
15+
g.add_adjacent_node('g_1', 4)
16+
assert g.g_1.data is 4
17+
assert str(g) == "('g', 0)"
18+
19+
def test_AdjacencyMatrixGraphNode():
20+
g = AdjacencyMatrixGraphNode('g', 3)
21+
assert str(g) == "('g', 3)"
22+
23+
def test_GraphEdge():
24+
g_1 = AdjacencyListGraphNode('g_1', 1)
25+
g_2 = AdjacencyListGraphNode('g_2', 2)
26+
e = GraphEdge(g_1, g_2, value=2)
27+
assert str(e) == "('g_1', 'g_2')"

0 commit comments

Comments
 (0)