Skip to content

Added Graphs #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pydatastructs/graphs/__init__.py
Original file line number Diff line number Diff line change
@@ -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__)
55 changes: 55 additions & 0 deletions pydatastructs/graphs/adjacency_list.py
Original file line number Diff line number Diff line change
@@ -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)
103 changes: 103 additions & 0 deletions pydatastructs/graphs/graph.py
Original file line number Diff line number Diff line change
@@ -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.")
Empty file.
34 changes: 34 additions & 0 deletions pydatastructs/graphs/tests/test_adjacency_list.py
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion pydatastructs/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from .misc_util import (
TreeNode,
LinkedListNode,
BinomialTreeNode
BinomialTreeNode,
AdjacencyListGraphNode,
AdjacencyMatrixGraphNode,
GraphEdge
)
__all__.extend(misc_util.__all__)
110 changes: 106 additions & 4 deletions pydatastructs/utils/misc_util.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
__all__ = [
'TreeNode',
'LinkedListNode',
'BinomialTreeNode'
'BinomialTreeNode',
'AdjacencyListGraphNode',
'AdjacencyMatrixGraphNode',
'GraphEdge'
]

_check_type = lambda a, t: isinstance(a, t)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand All @@ -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))
27 changes: 27 additions & 0 deletions pydatastructs/utils/tests/test_misc_util.py
Original file line number Diff line number Diff line change
@@ -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')"