Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ sudo: false
language: python

python:
- "3.5"
- "3.6"
install:
- pip install -r requirements.txt

Expand Down
3 changes: 2 additions & 1 deletion pydatastructs/graphs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from . import algorithms
from .algorithms import (
breadth_first_search,
breadth_first_search_parallel
breadth_first_search_parallel,
minimum_spanning_tree
)

__all__.extend(algorithms.__all__)
14 changes: 13 additions & 1 deletion pydatastructs/graphs/adjacency_list.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pydatastructs.graphs.graph import Graph
from pydatastructs.linear_data_structures import DynamicOneDimensionalArray
from pydatastructs.utils.misc_util import GraphEdge

__all__ = [
'AdjacencyList'
Expand All @@ -19,6 +20,7 @@ def __new__(cls, *vertices):
for vertex in vertices:
obj.__setattr__(vertex.name, vertex)
obj.vertices = set([vertex.name for vertex in vertices])
obj.edge_weights = dict()
return obj

def is_adjacent(self, node1, node2):
Expand All @@ -42,14 +44,24 @@ def remove_vertex(self, name):
delattr(node_obj, name)
node_obj.adjacent.remove(name)

def add_edge(self, source, target):
def add_edge(self, source, target, cost=None):
source, target = self.__getattribute__(source), \
self.__getattribute__(target)
source.__setattr__(target.name, target)
source.adjacent.add(target.name)
if cost is not None:
self.edge_weights[source.name + "_" + target.name] = \
GraphEdge(source, target, cost)

def get_edge(self, source, target):
return self.edge_weights.get(
source + "_" + target,
None)

def remove_edge(self, source, target):
source, target = self.__getattribute__(source), \
self.__getattribute__(target)
source.adjacent.remove(target.name)
delattr(source, target.name)
self.edge_weights.pop(source.name + "_" + target.name,
None)
26 changes: 19 additions & 7 deletions pydatastructs/graphs/adjacency_matrix.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pydatastructs.graphs.graph import Graph
from pydatastructs.linear_data_structures import OneDimensionalArray
from pydatastructs.utils.misc_util import AdjacencyMatrixGraphNode
from pydatastructs.utils.misc_util import AdjacencyMatrixGraphNode, GraphEdge

__all__ = [
'AdjacencyMatrix'
Expand All @@ -18,11 +18,9 @@ class AdjacencyMatrix(Graph):
def __new__(cls, *vertices):
obj = object.__new__(cls)
num_vertices = len(vertices)
obj.vertices = OneDimensionalArray(
AdjacencyMatrixGraphNode,
num_vertices)
obj.vertices = [vertex.name for vertex in vertices]
for vertex in vertices:
obj.vertices[vertex.name] = vertex
obj.__setattr__(str(vertex.name), vertex)
obj.matrix = OneDimensionalArray(
OneDimensionalArray,
num_vertices)
Expand All @@ -31,6 +29,7 @@ def __new__(cls, *vertices):
bool,
num_vertices)
obj.matrix[i].fill(False)
obj.edge_weights = dict()
return obj

def is_adjacent(self, node1, node2):
Expand All @@ -40,7 +39,8 @@ def neighbors(self, node):
neighbors = []
for i in range(self.matrix[node]._size):
if self.matrix[node][i]:
neighbors.append(self.vertices[i])
neighbors.append(self.__getattribute__(
str(self.vertices[i])))
return neighbors

def add_vertex(self, node):
Expand All @@ -51,8 +51,20 @@ def remove_vertex(self, node):
raise NotImplementedError("Currently we allow "
"adjacency matrix for static graphs only.")

def add_edge(self, source, target):
def add_edge(self, source, target, cost=None):
self.matrix[source][target] = True
source, target = str(source), str(target)
if cost is not None:
self.edge_weights[source + "_" + target] = \
GraphEdge(self.__getattribute__(source),
self.__getattribute__(target),
cost)

def get_edge(self, source, target):
return self.edge_weights.get(
str(source) + "_" + str(target),
None)

def remove_edge(self, source, target):
self.matrix[source][target] = False
self.edge_weights.pop(str(source) + "_" + str(target), None)
87 changes: 85 additions & 2 deletions pydatastructs/graphs/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
data structure.
"""
from collections import deque as Queue
from pydatastructs.utils.misc_util import AdjacencyListGraphNode
from concurrent.futures import ThreadPoolExecutor
from pydatastructs.utils import GraphEdge
from pydatastructs.miscellaneous_data_structures import DisjointSetForest
from pydatastructs.graphs.graph import Graph

__all__ = [
'breadth_first_search',
'breadth_first_search_parallel'
'breadth_first_search_parallel',
'minimum_spanning_tree'
]

def breadth_first_search(
Expand Down Expand Up @@ -186,3 +189,83 @@ def _breadth_first_search_parallel_adjacency_list(
return None

_breadth_first_search_parallel_adjacency_matrix = _breadth_first_search_parallel_adjacency_list

def _minimum_spanning_tree_kruskal_adjacency_list(graph):
mst = Graph(*[getattr(graph, v) for v in graph.vertices])
sort_key = lambda item: item[1].value
dsf = DisjointSetForest()
for v in graph.vertices:
dsf.make_set(v)
for _, edge in sorted(graph.edge_weights.items(), key=sort_key):
u, v = edge.source.name, edge.target.name
if dsf.find_root(u) is not dsf.find_root(v):
mst.add_edge(u, v, edge.value)
dsf.union(u, v)
return mst

def _minimum_spanning_tree_kruskal_adjacency_matrix(graph):
mst = Graph(*[getattr(graph, str(v)) for v in graph.vertices])
sort_key = lambda item: item[1].value
dsf = DisjointSetForest()
for v in graph.vertices:
dsf.make_set(v)
for _, edge in sorted(graph.edge_weights.items(), key=sort_key):
u, v = edge.source.name, edge.target.name
if dsf.find_root(u) is not dsf.find_root(v):
mst.add_edge(u, v, edge.value)
dsf.union(u, v)
return mst

def minimum_spanning_tree(graph, algorithm):
"""
Computes a minimum spanning tree for the given
graph and algorithm.

Parameters
==========

graph: Graph
The graph whose minimum spanning tree
has to be computed.
algorithm: str
The algorithm which should be used for
computing a minimum spanning tree.
Currently the following algorithms are
supported,
'kruskal' -> Kruskal's algorithm as given in
[1].

Returns
=======

mst: Graph
A minimum spanning tree using the implementation
same as the graph provided in the input.

Examples
========

>>> from pydatastructs import Graph, AdjacencyListGraphNode
>>> from pydatastructs import minimum_spanning_tree
>>> u = AdjacencyListGraphNode('u')
>>> v = AdjacencyListGraphNode('v')
>>> G = Graph(u, v)
>>> G.add_edge(u.name, v.name, 3)
>>> mst = minimum_spanning_tree(G, 'kruskal')
>>> u_n = mst.neighbors(u.name)
>>> mst.get_edge(u.name, u_n[0].name).value
3

References
==========

.. [1] https://en.wikipedia.org/wiki/Kruskal%27s_algorithm
"""
import pydatastructs.graphs.algorithms as algorithms
func = "_minimum_spanning_tree_" + algorithm + "_" + graph._impl
if not hasattr(algorithms, func):
raise NotImplementedError(
"Currently %s algoithm for %s implementation of graphs "
"isn't implemented for finding minimum spanning trees."
%(algorithm, graph._impl))
return getattr(algorithms, func)(graph)
11 changes: 10 additions & 1 deletion pydatastructs/graphs/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def remove_vertex(self, node):
raise NotImplementedError(
"This is an abstract method.")

def add_edge(self, source, target):
def add_edge(self, source, target, cost=None):
"""
Adds the edge starting at first parameter
i.e., source and ending at the second
Expand All @@ -105,6 +105,15 @@ def add_edge(self, source, target):
raise NotImplementedError(
"This is an abstract method.")

def get_edge(self, source, target):
"""
Returns GraphEdge object if there
is an edge between source and target
otherwise None.
"""
raise NotImplementedError(
"This is an abstract method.")

def remove_edge(self, source, target):
"""
Removes the edge starting at first parameter
Expand Down
12 changes: 9 additions & 3 deletions pydatastructs/graphs/tests/test_adjacency_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ def test_adjacency_list():
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')
g.add_edge('v_1', 'v', 0)
g.add_edge('v_2', 'v', 0)
g.add_edge('v_3', 'v', 0)
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
e1 = g.get_edge('v_1', 'v')
e2 = g.get_edge('v_2', 'v')
e3 = g.get_edge('v_3', 'v')
assert (e1.source.name, e1.target.name) == ('v_1', 'v')
assert (e2.source.name, e2.target.name) == ('v_2', 'v')
assert (e3.source.name, e3.target.name) == ('v_3', 'v')
g.remove_edge('v_1', 'v')
assert g.is_adjacent('v_1', 'v') is False
g.remove_vertex('v')
Expand Down
14 changes: 10 additions & 4 deletions pydatastructs/graphs/tests/test_adjacency_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ def test_AdjacencyMatrix():
v_0 = AdjacencyMatrixGraphNode(0, 0)
v_1 = AdjacencyMatrixGraphNode(1, 1)
v_2 = AdjacencyMatrixGraphNode(2, 2)
g = Graph(v_0, v_1, v_2, implementation='adjacency_matrix')
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 0)
g = Graph(v_0, v_1, v_2)
g.add_edge(0, 1, 0)
g.add_edge(1, 2, 0)
g.add_edge(2, 0, 0)
e1 = g.get_edge(0, 1)
e2 = g.get_edge(1, 2)
e3 = g.get_edge(2, 0)
assert (e1.source.name, e1.target.name) == (0, 1)
assert (e2.source.name, e2.target.name) == (1, 2)
assert (e3.source.name, e3.target.name) == (2, 0)
assert g.is_adjacent(0, 1) is True
assert g.is_adjacent(1, 2) is True
assert g.is_adjacent(2, 0) is True
Expand Down
47 changes: 37 additions & 10 deletions pydatastructs/graphs/tests/test_algorithms.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from pydatastructs import (breadth_first_search, Graph,
breadth_first_search_parallel)
breadth_first_search_parallel, minimum_spanning_tree)


def test_breadth_first_search():

def _test_breadth_first_search(ds, impl):
def _test_breadth_first_search(ds):
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)
G1 = Graph(V1, V2, V3)

edges = [
(V1.name, V2.name),
Expand Down Expand Up @@ -47,7 +47,7 @@ def bfs_tree(curr_node, next_node, parent):
(V7.name, V8.name)
]

G2 = Graph(V4, V5, V6, V7, V8, implementation=impl)
G2 = Graph(V4, V5, V6, V7, V8)

for edge in edges:
G2.add_edge(*edge)
Expand All @@ -71,12 +71,12 @@ def path_finder(curr_node, next_node, dest_node, parent, path):
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")
_test_breadth_first_search("List")
_test_breadth_first_search("Matrix")

def test_breadth_first_search_parallel():

def _test_breadth_first_search_parallel(ds, impl):
def _test_breadth_first_search_parallel(ds):
import pydatastructs.utils.misc_util as utils
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")

Expand All @@ -90,7 +90,7 @@ def _test_breadth_first_search_parallel(ds, impl):
V8 = GraphNode(7)


G1 = Graph(V1, V2, V3, V4, V5, V6, V7, V8, implementation=impl)
G1 = Graph(V1, V2, V3, V4, V5, V6, V7, V8)

edges = [
(V1.name, V2.name),
Expand Down Expand Up @@ -119,5 +119,32 @@ def bfs_tree(curr_node, next_node, parent):
(parent[V6.name] in (V2.name, V3.name)) and
(parent[V7.name] in (V3.name, V4.name)) and (parent[V8.name] == V4.name))

_test_breadth_first_search_parallel("List", "adjacency_list")
_test_breadth_first_search_parallel("Matrix", "adjacency_matrix")
_test_breadth_first_search_parallel("List")
_test_breadth_first_search_parallel("Matrix")

def test_minimum_spanning_tree():

def _test_minimum_spanning_tree(ds, algorithm):
import pydatastructs.utils.misc_util as utils
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
a, b, c, d, e = [GraphNode(x) for x in [0, 1, 2, 3, 4]]
graph = Graph(a, b, c, d, e)
graph.add_edge(a.name, c.name, 10)
graph.add_edge(c.name, a.name, 10)
graph.add_edge(a.name, d.name, 7)
graph.add_edge(d.name, a.name, 7)
graph.add_edge(c.name, d.name, 9)
graph.add_edge(d.name, c.name, 9)
graph.add_edge(d.name, b.name, 32)
graph.add_edge(b.name, d.name, 32)
graph.add_edge(d.name, e.name, 23)
graph.add_edge(e.name, d.name, 23)
mst = minimum_spanning_tree(graph, algorithm=algorithm)
expected_mst = [('0_3', 7), ('2_3', 9), ('3_4', 23), ('3_1', 32),
('3_0', 7), ('3_2', 9), ('4_3', 23), ('1_3', 32)]
assert len(expected_mst) == 2*len(mst.edge_weights.items())
for k, v in mst.edge_weights.items():
assert (k, v.value) in expected_mst

_test_minimum_spanning_tree("List", "kruskal")
_test_minimum_spanning_tree("Matrix", "kruskal")
8 changes: 4 additions & 4 deletions pydatastructs/utils/misc_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ class GraphEdge(object):
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.
node1: str/int
The name of the source node of the edge.
node2: str/int
The name of the target node of the edge.
"""
def __new__(cls, node1, node2, value=None):
obj = object.__new__(cls)
Expand Down