diff --git a/README.md b/README.md index 09e9b6dc9..803c88b0e 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,11 @@ Keep contributing!! Thanks to these wonderful people ✨✨: - - - + + +
- - - -
+ + + +
diff --git a/docs/requirements.txt b/docs/requirements.txt index b3ecdb274..6526a0ef8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx==4.2.0 -sphinx-readable-theme==1.3.0 \ No newline at end of file +sphinx-readable-theme==1.3.0 +myst_nb==0.13.1 \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 1b4721a6e..f096ee613 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ master_doc = 'index' # The full version, including alpha/beta/rc tags -release = '0.0.1-dev' +release = '1.0.1-dev' # -- General configuration --------------------------------------------------- @@ -36,9 +36,12 @@ # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.napoleon' + 'sphinx.ext.napoleon', + 'myst_nb' ] +jupyter_execute_notebooks = "off" + napoleon_numpy_docstring = True # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/index.rst b/docs/source/index.rst index cdcaef0a4..9a12f0d64 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,6 +40,21 @@ the other, Make sure that your python version is at least ``3.8``. +Why PyDataStructs? +================== + +1. **Single package for all your data structures and algorithms** - We have and are + implementing many popular and useful data structures and algorithms. + +2. **Consistent and Clean Interface** - The APIs we have provided are **consistent** with each other, + **clean** and **easy to use**. We make sure of that before adding any new data structure or algorithm. + +3. **Well Tested** - We thoroughly test our code before making any new addition to PyDataStructs. + **99 percent** lines of our code have already been tested by us. + +So, **you can easily rely on PyDataStructs** for any data structure or algorithm you want to use +**without worrying about implementing** it **from scratch**. Everything is just a few calls away. + Why do we use Python? ===================== @@ -59,6 +74,7 @@ Contents :maxdepth: 1 tutorials.rst + pydatastructs_sphinx_graphs contributing.rst authors.rst pydatastructs/pydatastructs.rst diff --git a/docs/source/pydatastructs_sphinx_graphs.ipynb b/docs/source/pydatastructs_sphinx_graphs.ipynb new file mode 100644 index 000000000..724c0a07c --- /dev/null +++ b/docs/source/pydatastructs_sphinx_graphs.ipynb @@ -0,0 +1,243 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "pydatastructs-sphinx-graphs.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "2qB4MTFoYSdW" + }, + "source": [ + "Comparing Dijkstra and Bellman Ford Shortest Paths Algorithms using PyDataStructs\n", + "====================================" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Zu6G_1RLYitv" + }, + "source": [ + "Dataset\n", + "-------\n", + "\n", + "We have used [California road network](https://snap.stanford.edu/data/roadNet-CA.html) from [Stanford Network Analysis Project](https://snap.stanford.edu/index.html). The intent of this demo is to show how **pydatastructs** can be used for research and analysis purposes.\n", + "\n", + "The above dataset is a road network of California as the name suggests. The intersections and endpoints in this network are represented as vertices and the roads between them are represented as undirected edges. The data is read from a `txt` file where each line contains two numbers representing two points of an edge in the graph. We have used varying number of these edges to analyse how each algorithm responds to the varying scale of the shortest path problem at hand." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YoaukKUfaF66" + }, + "source": [ + "Results\n", + "-------\n", + "\n", + "We observed that for low inverse density (total number of possible edges divided by number of edges present) graphs, both algorithms take similar amounts of time. However Dijkstra algorithm performs significantly better with high inverse density graphs as compared to Bellman Ford algorithm." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "_qWIIix_Twd5" + }, + "source": [ + "# Import modules and APIs for Graphs\n", + "from pydatastructs import Graph, AdjacencyListGraphNode\n", + "from pydatastructs import shortest_paths, topological_sort\n", + "\n", + "# Import utility modules\n", + "import timeit, random, functools, matplotlib.pyplot as plt" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "8TDkoIgcXJr6" + }, + "source": [ + "def create_Graph(num_edges, file_path, ignore_lines=4):\n", + " \"\"\"\n", + " Creates pydatastructs.Graph object.\n", + "\n", + " Parameters\n", + " ==========\n", + "\n", + " num_edges: int\n", + " Number of edges that should be present in the\n", + " pydatastructs.Graph object.\n", + " file_path: str\n", + " The path to the file containing California\n", + " road network dataset.\n", + " ignore_lines: int\n", + " Number of inital lines that should be ignored.\n", + " Optional, by default 4 because the first 4 lines\n", + " contain documentation of the dataset which is not\n", + " required to generate the pydatastructs.Graph object.\n", + " \n", + " Returns\n", + " =======\n", + "\n", + " G: pydatastructs.Graph\n", + " \"\"\"\n", + " f = open(file_path, 'r')\n", + " for _ in range(ignore_lines):\n", + " f.readline()\n", + " G = Graph()\n", + " inp = f.readline().split()\n", + " for _ in range(num_edges):\n", + " u, v = inp\n", + " G.add_vertex(AdjacencyListGraphNode(u))\n", + " G.add_vertex(AdjacencyListGraphNode(v))\n", + " G.add_edge(u, v, random.randint(1, 1000)) \n", + " inp = f.readline().split()\n", + " return G" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "qRS4Rz-ZRZ51" + }, + "source": [ + "def generate_data(file_name, min_num_edges, max_num_edges, increment):\n", + " \"\"\"\n", + " Generates computation time data for Dijkstra and Bellman ford\n", + " algorithms using pydatastructs.shortest_paths.\n", + "\n", + " Parameters\n", + " ==========\n", + "\n", + " file_path: str\n", + " The path to the file containing California\n", + " road network dataset.\n", + " min_num_edges: int\n", + " The minimum number of edges to be used for\n", + " comparison of algorithms.\n", + " max_num_edges: int\n", + " The maximum number of edges to be used for comparison\n", + " of algorithms.\n", + " increment: int\n", + " The value to be used to increment the scale of the\n", + " shortest path problem. For example if using 50 edges,\n", + " and increment value is 10, then in the next iteration,\n", + " 60 edges will be used and in the next to next iteration,\n", + " 70 edges will be used and so on until we hit the max_num_edges\n", + " value.\n", + "\n", + " Returns\n", + " =======\n", + "\n", + " graph_data, data_dijkstra, data_bellman_ford: (list, list, list)\n", + " graph_data contains tuples of number of vertices and number\n", + " of edges.\n", + " data_dijkstra contains the computation time values for each\n", + " graph when Dijkstra algorithm is used.\n", + " data_bellman_ford contains the computation time values for each\n", + " graph when Bellman ford algorithm is used. \n", + " \"\"\"\n", + " data_dijkstra, data_bellman_ford, graph_data = [], [], []\n", + " for edge in range(min_num_edges, max_num_edges + 1, increment):\n", + " G = create_Graph(edge, file_name)\n", + " t = timeit.Timer(functools.partial(shortest_paths, G, 'dijkstra', '1'))\n", + " t_djk = t.repeat(1, 1)\n", + " t = timeit.Timer(functools.partial(shortest_paths, G, 'bellman_ford', '1'))\n", + " t_bf = t.repeat(1, 1)\n", + " graph_data.append((len(G.vertices), len(G.edge_weights)))\n", + " data_dijkstra.append(t_djk[0])\n", + " data_bellman_ford.append(t_bf[0])\n", + " return graph_data, data_dijkstra, data_bellman_ford" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "GTeSF1ChA2Bz" + }, + "source": [ + "def plot_data(graph_data, data_dijkstra, data_bellman_ford):\n", + " \"\"\"\n", + " Utility function to plot the computation time values\n", + " for Dijkstra and Bellman ford algorithms versus\n", + " the inverse density of the input graph.\n", + " \"\"\"\n", + " idensity, time_dijkstra, time_bellman_ford = [], [], []\n", + " for datum_graph, datum_djk, datum_bf in zip(graph_data, data_dijkstra, data_bellman_ford):\n", + " num_edges, num_vertices = datum_graph[1], datum_graph[0]\n", + " idensity.append((num_vertices*(num_vertices - 1))/(2*num_edges))\n", + " time_dijkstra.append(datum_djk)\n", + " time_bellman_ford.append(datum_bf)\n", + " plt.xlabel(\"Inverse Density of Input Graph\")\n", + " plt.ylabel(\"Computation Time (s)\")\n", + " plt.plot(idensity, time_dijkstra, label=\"Dijkstra\")\n", + " plt.plot(idensity, time_bellman_ford, label=\"Bellman Ford\")\n", + " plt.legend(loc=\"best\")\n", + " plt.show()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "UXqC736NXfs2" + }, + "source": [ + "graph_data, data_djk, data_bf = generate_data('roadNet-CA.txt', 50, 2000, 50)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 300 + }, + "id": "EEEPGwOpV_DC", + "outputId": "8e84de7b-c905-4cd1-e075-514982c2ef22" + }, + "source": [ + "plot_data(graph_data, data_djk, data_bf)" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index 704b4b19c..519b9ac6a 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -4,7 +4,7 @@ Tutorials We provide the following tutorials to show how ``pydatastructs`` APIs can help in solving complicated data structures and algorithms problems easily. For now the problems are abstract. However, we plan -to add some examples showing usage of ``pydatastructs`` on real world +to add some more examples showing usage of ``pydatastructs`` on real world data sets such as `Stanford Large Network Dataset Collection `_ and `Urban Dictionary Words And Definitions `_. If you are interested in playing around with the above datasets using our API, diff --git a/pydatastructs/__init__.py b/pydatastructs/__init__.py index 5568ebd3d..41f351945 100644 --- a/pydatastructs/__init__.py +++ b/pydatastructs/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.1-dev" +__version__ = "1.0.1-dev" from .linear_data_structures import * from .trees import * diff --git a/pydatastructs/graphs/adjacency_list.py b/pydatastructs/graphs/adjacency_list.py index a06e35533..541ff3a1e 100644 --- a/pydatastructs/graphs/adjacency_list.py +++ b/pydatastructs/graphs/adjacency_list.py @@ -26,8 +26,8 @@ def __new__(cls, *vertices): @classmethod def methods(self): return ['is_adjacent', 'neighbors', - 'add_vertex', 'remove_vertex', 'add_edge', - 'get_edge', 'remove_edge', '__new__'] + 'add_vertex', 'remove_vertex', 'add_edge', + 'get_edge', 'remove_edge', '__new__'] def is_adjacent(self, node1, node2): node1 = self.__getattribute__(node1) @@ -52,8 +52,21 @@ def remove_vertex(self, name): node_obj.adjacent.remove(name) def add_edge(self, source, target, cost=None): + source, target = str(source), str(target) + error_msg = ("Vertex %s is not present in the graph." + "Call Graph.add_vertex to add a new" + "vertex. Graph.add_edge is only responsible" + "for adding edges and it will not add new" + "vertices on its own. This is done to maintain" + "clear separation between the functionality of" + "these two methods.") + if not hasattr(self, source): + raise ValueError(error_msg % (source)) + if not hasattr(self, target): + raise ValueError(error_msg % (target)) + source, target = self.__getattribute__(source), \ - self.__getattribute__(target) + self.__getattribute__(target) source.add_adjacent_node(target.name) if cost is not None: self.edge_weights[source.name + "_" + target.name] = \ diff --git a/pydatastructs/graphs/adjacency_matrix.py b/pydatastructs/graphs/adjacency_matrix.py index a75efabdf..64a39494c 100644 --- a/pydatastructs/graphs/adjacency_matrix.py +++ b/pydatastructs/graphs/adjacency_matrix.py @@ -57,6 +57,18 @@ def remove_vertex(self, node): def add_edge(self, source, target, cost=None): source, target = str(source), str(target) + error_msg = ("Vertex %s is not present in the graph." + "Call Graph.add_vertex to add a new" + "vertex. Graph.add_edge is only responsible" + "for adding edges and it will not add new" + "vertices on its own. This is done to maintain" + "clear separation between the functionality of" + "these two methods.") + if source not in self.matrix: + raise ValueError(error_msg % (source)) + if target not in self.matrix: + raise ValueError(error_msg % (target)) + self.matrix[source][target] = True if cost is not None: self.edge_weights[source + "_" + target] = \ diff --git a/pydatastructs/graphs/graph.py b/pydatastructs/graphs/graph.py index f19fc8e97..0c1b195a8 100644 --- a/pydatastructs/graphs/graph.py +++ b/pydatastructs/graphs/graph.py @@ -49,6 +49,16 @@ class Graph(object): ========== .. [1] https://en.wikipedia.org/wiki/Graph_(abstract_data_type) + + Note + ==== + + Make sure to create nodes (AdjacencyListGraphNode or AdjacencyMatrixGraphNode) + and them in your graph using Graph.add_vertex before adding edges whose + end points require either of the nodes that you added. In other words, + Graph.add_edge doesn't add new nodes on its own if the input + nodes are not already present in the Graph. + """ __slots__ = ['_impl'] @@ -89,7 +99,8 @@ def neighbors(self, node): def add_vertex(self, node): """ - Adds the input vertex to the node. + Adds the input vertex to the node, or does nothing + if the input vertex is already in the graph. """ raise NotImplementedError( "This is an abstract method.") @@ -97,7 +108,7 @@ def add_vertex(self, node): def remove_vertex(self, node): """ Removes the input vertex along with all the edges - pointing towards to it. + pointing towards it. """ raise NotImplementedError( "This is an abstract method.") diff --git a/pydatastructs/graphs/tests/test_adjacency_list.py b/pydatastructs/graphs/tests/test_adjacency_list.py index d1635fc83..3dcef8a7a 100644 --- a/pydatastructs/graphs/tests/test_adjacency_list.py +++ b/pydatastructs/graphs/tests/test_adjacency_list.py @@ -1,5 +1,6 @@ from pydatastructs.graphs import Graph from pydatastructs.utils import AdjacencyListGraphNode +from pydatastructs.utils.raises_util import raises def test_adjacency_list(): v_1 = AdjacencyListGraphNode('v_1', 1) @@ -38,3 +39,6 @@ def test_adjacency_list(): g.remove_vertex('v') assert g.is_adjacent('v_2', 'v') is False assert g.is_adjacent('v_3', 'v') is False + + assert raises(ValueError, lambda: g.add_edge('u', 'v')) + assert raises(ValueError, lambda: g.add_edge('v', 'x')) diff --git a/pydatastructs/graphs/tests/test_adjacency_matrix.py b/pydatastructs/graphs/tests/test_adjacency_matrix.py index 3df819611..c0f7b7c06 100644 --- a/pydatastructs/graphs/tests/test_adjacency_matrix.py +++ b/pydatastructs/graphs/tests/test_adjacency_matrix.py @@ -1,5 +1,6 @@ from pydatastructs.graphs import Graph from pydatastructs.utils import AdjacencyMatrixGraphNode +from pydatastructs.utils.raises_util import raises def test_AdjacencyMatrix(): v_0 = AdjacencyMatrixGraphNode(0, 0) @@ -25,3 +26,6 @@ def test_AdjacencyMatrix(): assert neighbors == [v_1] g.remove_edge(0, 1) assert g.is_adjacent(0, 1) is False + + assert raises(ValueError, lambda: g.add_edge('u', 'v')) + assert raises(ValueError, lambda: g.add_edge('v', 'x')) diff --git a/pydatastructs/linear_data_structures/arrays.py b/pydatastructs/linear_data_structures/arrays.py index 32fa0ee42..e4bc24223 100644 --- a/pydatastructs/linear_data_structures/arrays.py +++ b/pydatastructs/linear_data_structures/arrays.py @@ -15,7 +15,8 @@ def __str__(self) -> str: class OneDimensionalArray(Array): ''' - Represents one dimensional arrays. + Represents one dimensional static arrays of + fixed size. Parameters ========== @@ -269,7 +270,8 @@ class DynamicArray(Array): class DynamicOneDimensionalArray(DynamicArray, OneDimensionalArray): """ - Represents dynamic one dimensional arrays. + Represents resizable and dynamic one + dimensional arrays. Parameters ========== diff --git a/pydatastructs/miscellaneous_data_structures/algorithms.py b/pydatastructs/miscellaneous_data_structures/algorithms.py index 5f2204a8a..1a4ffc56a 100644 --- a/pydatastructs/miscellaneous_data_structures/algorithms.py +++ b/pydatastructs/miscellaneous_data_structures/algorithms.py @@ -53,11 +53,11 @@ class RangeQueryStatic: >>> from pydatastructs import minimum >>> arr = OneDimensionalArray(int, [4, 6, 1, 5, 7, 3]) >>> RMQ = RangeQueryStatic(arr, minimum) - >>> RMQ.query(3, 5) + >>> RMQ.query(3, 4) 5 - >>> RMQ.query(0, 5) + >>> RMQ.query(0, 4) 1 - >>> RMQ.query(0, 3) + >>> RMQ.query(0, 2) 1 Note @@ -97,9 +97,7 @@ def query(start, end): start: int The starting index of the range. end: int - The index just before which the range ends. - This means that this index will be excluded - from the range for generating results. + The ending index of the range. """ raise NotImplementedError( "This is an abstract method.") @@ -121,7 +119,7 @@ def methods(cls): return ['query'] def query(self, start, end): - _check_range_query_inputs((start, end), self.bounds) + _check_range_query_inputs((start, end + 1), self.bounds) return self.sparse_table.query(start, end) @@ -140,14 +138,14 @@ def methods(cls): return ['query'] def query(self, start, end): - _check_range_query_inputs((start, end), (0, len(self.array))) + _check_range_query_inputs((start, end + 1), (0, len(self.array))) - rsize = end - start + rsize = end - start + 1 if rsize == 1: return self.func((self.array[start],)) query_ans = self.func((self.array[start], self.array[start + 1])) - for i in range(start + 2, end): + for i in range(start + 2, end + 1): query_ans = self.func((query_ans, self.array[i])) return query_ans diff --git a/pydatastructs/miscellaneous_data_structures/sparse_table.py b/pydatastructs/miscellaneous_data_structures/sparse_table.py index 02000724d..972612eb1 100644 --- a/pydatastructs/miscellaneous_data_structures/sparse_table.py +++ b/pydatastructs/miscellaneous_data_structures/sparse_table.py @@ -86,11 +86,8 @@ def query(self, start, end): start: int The starting index of the range. end: int - The index just before which the range ends. - This means that this index will be excluded - from the range for generating results. + The ending index of the range. """ - end -= 1 j = int(math.log2(end - start + 1)) + 1 answer = None while j >= 0: diff --git a/pydatastructs/miscellaneous_data_structures/tests/test_range_query_static.py b/pydatastructs/miscellaneous_data_structures/tests/test_range_query_static.py index 86212ab11..e898653c9 100644 --- a/pydatastructs/miscellaneous_data_structures/tests/test_range_query_static.py +++ b/pydatastructs/miscellaneous_data_structures/tests/test_range_query_static.py @@ -12,9 +12,9 @@ def _test_RangeQueryStatic_common(func, gen_expected): array = OneDimensionalArray(int, [1]) rq = RangeQueryStatic(array, func) - assert rq.query(0, 1) == 1 - raises(ValueError, lambda: rq.query(0, 0)) - raises(IndexError, lambda: rq.query(0, 2)) + assert rq.query(0, 0) == 1 + raises(ValueError, lambda: rq.query(0, -1)) + raises(IndexError, lambda: rq.query(0, 1)) array_sizes = [3, 6, 12, 24, 48, 96] random.seed(0) @@ -38,18 +38,18 @@ def _test_RangeQueryStatic_common(func, gen_expected): def test_RangeQueryStatic_minimum(): def _gen_minimum_expected(data, i, j): - return min(data[i:j]) + return min(data[i:j + 1]) _test_RangeQueryStatic_common(minimum, _gen_minimum_expected) def test_RangeQueryStatic_greatest_common_divisor(): def _gen_gcd_expected(data, i, j): - if j - i == 1: + if j == i: return data[i] else: expected_gcd = math.gcd(data[i], data[i + 1]) - for idx in range(i + 2, j): + for idx in range(i + 2, j + 1): expected_gcd = math.gcd(expected_gcd, data[idx]) return expected_gcd @@ -58,6 +58,6 @@ def _gen_gcd_expected(data, i, j): def test_RangeQueryStatic_summation(): def _gen_summation_expected(data, i, j): - return sum(data[i:j]) + return sum(data[i:j + 1]) return _test_RangeQueryStatic_common(summation, _gen_summation_expected) diff --git a/setup.py b/setup.py index 2d23ecdd0..45f293265 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,8 @@ long_description = fh.read() setuptools.setup( - name="pydatastructs", - version="0.0.1-dev", + name="cz-pydatastructs", + version="1.0.1-dev", author="PyDataStructs Development Team", author_email="pydatastructs@googlegroups.com", description="A python package for data structures", @@ -17,7 +17,6 @@ "Programming Language :: Python :: 3", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", - "Topic :: Education :: Data Structures", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Information Analysis", "Topic :: Software Development :: Libraries"