From 0b08df47f3795a4d20cb00760a51d393d71e3848 Mon Sep 17 00:00:00 2001 From: Rejoan Sardar Date: Sat, 8 Mar 2025 23:36:27 +0530 Subject: [PATCH 01/11] feat: add graph algorithem --- pydatastructs/graphs/algorithms.py | 79 ++++++++++++------- pydatastructs/graphs/tests/test_algorithms.py | 3 + 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index ea3322c02..80f82b54f 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -682,15 +682,12 @@ def _depth_first_search_adjacency_list( _depth_first_search_adjacency_matrix = _depth_first_search_adjacency_list -def shortest_paths(graph: Graph, algorithm: str, - source: str, target: str="", - **kwargs) -> tuple: +def shortest_paths(graph: Graph, algorithm: str, source: str, target: str="", **kwargs) -> tuple: """ Finds shortest paths in the given graph from a given source. Parameters ========== - graph: Graph The graph under consideration. algorithm: str @@ -700,8 +697,10 @@ def shortest_paths(graph: Graph, algorithm: str, 'bellman_ford' -> Bellman-Ford algorithm as given in [1]. 'dijkstra' -> Dijkstra algorithm as given in [2]. + + 'A_star' -> A* algorithm as given in [3]. source: str - The name of the source the node. + The name of the source node. target: str The name of the target node. Optional, by default, all pair shortest paths @@ -713,35 +712,12 @@ def shortest_paths(graph: Graph, algorithm: str, Returns ======= - (distances, predecessors): (dict, dict) If target is not provided and algorithm used - is 'bellman_ford'/'dijkstra'. + is 'bellman_ford'/'dijkstra'/'A_star'. (distances[target], predecessors): (float, dict) If target is provided and algorithm used is - 'bellman_ford'/'dijkstra'. - - Examples - ======== - - >>> from pydatastructs import Graph, AdjacencyListGraphNode - >>> from pydatastructs import shortest_paths - >>> V1 = AdjacencyListGraphNode("V1") - >>> V2 = AdjacencyListGraphNode("V2") - >>> V3 = AdjacencyListGraphNode("V3") - >>> G = Graph(V1, V2, V3) - >>> G.add_edge('V2', 'V3', 10) - >>> G.add_edge('V1', 'V2', 11) - >>> shortest_paths(G, 'bellman_ford', 'V1') - ({'V1': 0, 'V2': 11, 'V3': 21}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) - >>> shortest_paths(G, 'dijkstra', 'V1') - ({'V2': 11, 'V3': 21, 'V1': 0}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) - - References - ========== - - .. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm - .. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + 'bellman_ford'/'dijkstra'/'A_star'. """ raise_if_backend_is_not_python( shortest_paths, kwargs.get('backend', Backend.PYTHON)) @@ -811,6 +787,49 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str): _dijkstra_adjacency_matrix = _dijkstra_adjacency_list +def _a_star_adjacency_list(graph: Graph, source: str, target: str) -> tuple: + distances, predecessor = {}, {} + + for v in graph.vertices: + distances[v] = float('inf') + predecessor[v] = None + distances[source] = 0 + + from pydatastructs.miscellaneous_data_structures.queue import PriorityQueue + pq = PriorityQueue(implementation='binomial_heap') + pq.push(source, 0) + + def heuristic(node: str, goal: str) -> float: + try: + x1, y1 = map(int, node.split(',')) + x2, y2 = map(int, goal.split(',')) + return abs(x1 - x2) + abs(y1 - y2) + except ValueError: + raise ValueError(f"Invalid node format: {node}. Expected 'x,y'.") + + while not pq.is_empty: + current = pq.pop() + + if current == target: + return (distances[target], predecessor) + + neighbors = graph.neighbors(current) + for neighbor in neighbors: + edge = graph.get_edge(current, neighbor.name) + if edge: + new_dist = distances[current] + edge.value + if new_dist < distances[neighbor.name]: + distances[neighbor.name] = new_dist + predecessor[neighbor.name] = current + pq.push(neighbor.name, new_dist + heuristic(neighbor.name, target)) + + if distances[target] == float('inf'): + raise ValueError(f"Either source '{source}' and target '{target}' have no path between them.") + + return (distances, predecessor) + +_a_star_adjacency_matrix = _a_star_adjacency_list + def all_pair_shortest_paths(graph: Graph, algorithm: str, **kwargs) -> tuple: """ diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index fde3571da..79f4ac1cd 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -1,3 +1,4 @@ +import pytest from pydatastructs import (breadth_first_search, Graph, breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, @@ -321,6 +322,8 @@ def _test_shortest_paths_negative_edges(ds, algorithm): _test_shortest_paths_negative_edges("Matrix", 'bellman_ford') _test_shortest_paths_positive_edges("List", 'dijkstra') _test_shortest_paths_positive_edges("Matrix", 'dijkstra') + _test_shortest_paths_positive_edges("List", 'A_star') + _test_shortest_paths_positive_edges("Matrix", 'A_star') def test_all_pair_shortest_paths(): From f3fd19502d8b5225365f8b1e1745d714ab3d8dee Mon Sep 17 00:00:00 2001 From: Rejoan Sardar Date: Sat, 8 Mar 2025 23:50:51 +0530 Subject: [PATCH 02/11] feat: add graph algorithem --- pydatastructs/graphs/algorithms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 80f82b54f..af2a47c5f 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -723,10 +723,10 @@ def shortest_paths(graph: Graph, algorithm: str, source: str, target: str="", ** shortest_paths, kwargs.get('backend', Backend.PYTHON)) import pydatastructs.graphs.algorithms as algorithms func = "_" + algorithm + "_" + graph._impl + if algorithm not in ['bellman_ford', 'dijkstra', 'A_star']: + raise NotImplementedError(f"Algorithm {algorithm} is not implemented.") if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently %s algorithm isn't implemented for " - "finding shortest paths in graphs."%(algorithm)) + raise NotImplementedError(f"Currently {algorithm} algorithm isn't implemented for finding shortest paths in graphs.") return getattr(algorithms, func)(graph, source, target) def _bellman_ford_adjacency_list(graph: Graph, source: str, target: str) -> tuple: From e31ba9cfaf80ee68267948f78411ddd0016b775f Mon Sep 17 00:00:00 2001 From: Rejoan Sardar Date: Sun, 9 Mar 2025 00:05:41 +0530 Subject: [PATCH 03/11] feat: add graph algorithem --- pydatastructs/graphs/algorithms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index af2a47c5f..f3d9db200 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -797,7 +797,7 @@ def _a_star_adjacency_list(graph: Graph, source: str, target: str) -> tuple: from pydatastructs.miscellaneous_data_structures.queue import PriorityQueue pq = PriorityQueue(implementation='binomial_heap') - pq.push(source, 0) + pq.push(source, distances[source] + heuristic(source, target)) # Fixed push def heuristic(node: str, goal: str) -> float: try: @@ -828,7 +828,7 @@ def heuristic(node: str, goal: str) -> float: return (distances, predecessor) -_a_star_adjacency_matrix = _a_star_adjacency_list +_a_star_adjacency_matrix = _a_star_adjacency_list # Ensure matrix version exists def all_pair_shortest_paths(graph: Graph, algorithm: str, **kwargs) -> tuple: From ff0d7c811bcdca6270fa8777652e6e3e68eea1bb Mon Sep 17 00:00:00 2001 From: Rejoan Sardar Date: Sun, 9 Mar 2025 00:29:57 +0530 Subject: [PATCH 04/11] feat: add graph algorithem --- pydatastructs/graphs/algorithms.py | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index f3d9db200..90cf4ecb5 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -722,11 +722,11 @@ def shortest_paths(graph: Graph, algorithm: str, source: str, target: str="", ** raise_if_backend_is_not_python( shortest_paths, kwargs.get('backend', Backend.PYTHON)) import pydatastructs.graphs.algorithms as algorithms - func = "_" + algorithm + "_" + graph._impl - if algorithm not in ['bellman_ford', 'dijkstra', 'A_star']: - raise NotImplementedError(f"Algorithm {algorithm} is not implemented.") + func = "_" + algorithm.lower() + "_" + graph._impl if not hasattr(algorithms, func): - raise NotImplementedError(f"Currently {algorithm} algorithm isn't implemented for finding shortest paths in graphs.") + raise NotImplementedError( + "Currently %s algorithm isn't implemented for " + "finding shortest paths in graphs."%(algorithm)) return getattr(algorithms, func)(graph, source, target) def _bellman_ford_adjacency_list(graph: Graph, source: str, target: str) -> tuple: @@ -797,22 +797,23 @@ def _a_star_adjacency_list(graph: Graph, source: str, target: str) -> tuple: from pydatastructs.miscellaneous_data_structures.queue import PriorityQueue pq = PriorityQueue(implementation='binomial_heap') - pq.push(source, distances[source] + heuristic(source, target)) # Fixed push + pq.push(source, distances[source]) def heuristic(node: str, goal: str) -> float: + """Manhattan distance heuristic for A*""" try: - x1, y1 = map(int, node.split(',')) - x2, y2 = map(int, goal.split(',')) - return abs(x1 - x2) + abs(y1 - y2) + if "," in node and "," in goal: # Check if node names are in "x,y" format + x1, y1 = map(int, node.split(',')) + x2, y2 = map(int, goal.split(',')) + return abs(x1 - x2) + abs(y1 - y2) + else: + return 0 # If not in coordinate format, return 0 heuristic except ValueError: - raise ValueError(f"Invalid node format: {node}. Expected 'x,y'.") + return 0 # Fallback heuristic if parsing fails while not pq.is_empty: current = pq.pop() - if current == target: - return (distances[target], predecessor) - neighbors = graph.neighbors(current) for neighbor in neighbors: edge = graph.get_edge(current, neighbor.name) @@ -823,10 +824,15 @@ def heuristic(node: str, goal: str) -> float: predecessor[neighbor.name] = current pq.push(neighbor.name, new_dist + heuristic(neighbor.name, target)) - if distances[target] == float('inf'): - raise ValueError(f"Either source '{source}' and target '{target}' have no path between them.") + # ✅ Handle case when target is empty (all-pairs shortest paths) + if target == "": + return (distances, predecessor) - return (distances, predecessor) + # ✅ Handle no path found case properly + if target not in distances or distances[target] == float('inf'): + return (float('inf'), predecessor) + + return (distances[target], predecessor) _a_star_adjacency_matrix = _a_star_adjacency_list # Ensure matrix version exists From 456eb6693f2ba03297b25fd77b9d03297c1d3a37 Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Sun, 9 Mar 2025 02:10:46 +0530 Subject: [PATCH 05/11] "feat: add graph algorithem" --- pydatastructs/graphs/algorithms.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 90cf4ecb5..86624cf5c 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -718,6 +718,29 @@ def shortest_paths(graph: Graph, algorithm: str, source: str, target: str="", ** (distances[target], predecessors): (float, dict) If target is provided and algorithm used is 'bellman_ford'/'dijkstra'/'A_star'. + + Examples + ======== + >>> from pydatastructs import Graph, AdjacencyListGraphNode + >>> from pydatastructs import shortest_paths + >>> V1 = AdjacencyListGraphNode("V1") + >>> V2 = AdjacencyListGraphNode("V2") + >>> V3 = AdjacencyListGraphNode("V3") + >>> G = Graph(V1, V2, V3) + >>> G.add_edge('V2', 'V3', 10) + >>> G.add_edge('V1', 'V2', 11) + >>> shortest_paths(G, 'bellman_ford', 'V1') + ({'V1': 0, 'V2': 11, 'V3': 21}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) + >>> shortest_paths(G, 'dijkstra', 'V1') + ({'V2': 11, 'V3': 21, 'V1': 0}, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) + >>> shortest_paths(G, 'A_star', 'V1', 'V3') + (21, {'V1': None, 'V2': 'V1', 'V3': 'V2'}) + + References + ========== + .. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm + .. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm + .. [3] https://en.wikipedia.org/wiki/A*_search_algorithm """ raise_if_backend_is_not_python( shortest_paths, kwargs.get('backend', Backend.PYTHON)) From 5e4608b9a4861dc4ee22f7fa114694597823f423 Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Sun, 9 Mar 2025 02:21:43 +0530 Subject: [PATCH 06/11] Update algorithms.py --- pydatastructs/graphs/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 86624cf5c..5231b30b3 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -682,7 +682,7 @@ def _depth_first_search_adjacency_list( _depth_first_search_adjacency_matrix = _depth_first_search_adjacency_list -def shortest_paths(graph: Graph, algorithm: str, source: str, target: str="", **kwargs) -> tuple: +def shortest_paths(graph: Graph, algorithm: str, source: str, target: str = "", **kwargs) -> tuple: """ Finds shortest paths in the given graph from a given source. From 347bdb76124d50783f0a6fe9cf99d13923633dcf Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Sun, 9 Mar 2025 02:25:01 +0530 Subject: [PATCH 07/11] feat: add graph algorithem --- pydatastructs/graphs/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 5231b30b3..c5d1dd0fa 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -718,7 +718,7 @@ def shortest_paths(graph: Graph, algorithm: str, source: str, target: str = "", (distances[target], predecessors): (float, dict) If target is provided and algorithm used is 'bellman_ford'/'dijkstra'/'A_star'. - + Examples ======== >>> from pydatastructs import Graph, AdjacencyListGraphNode From 3551dc4ec0657f13f6afe3fb537e1d45d4715aba Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:48:22 +0530 Subject: [PATCH 08/11] Update algorithms.py --- pydatastructs/graphs/algorithms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index c5d1dd0fa..cc93169c3 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -13,8 +13,8 @@ from pydatastructs import PriorityQueue __all__ = [ - 'breadth_first_search', - 'breadth_first_search_parallel', + 'breadth_firjhst_search', + 'breadth_first_search_jparallel', 'minimum_spanning_tree', 'minimum_spanning_tree_parallel', 'strongly_connected_components', From 2914db6fdd083f1f2931eb980851abe5fb6b1b51 Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:50:25 +0530 Subject: [PATCH 09/11] Update algorithms.py --- pydatastructs/graphs/algorithms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index cc93169c3..1c9c39863 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -2,6 +2,7 @@ Contains algorithms associated with graph data structure. """ +import os from collections import deque from concurrent.futures import ThreadPoolExecutor from pydatastructs.utils.misc_util import ( From 19bba3a1e2b7dabc1f446b825bdda61885a734ee Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:51:30 +0530 Subject: [PATCH 10/11] Update algorithms.py --- pydatastructs/graphs/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 1c9c39863..92e5f0272 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -3,7 +3,7 @@ data structure. """ import os -from collections import deque +from collections import deques from concurrent.futures import ThreadPoolExecutor from pydatastructs.utils.misc_util import ( _comp, raise_if_backend_is_not_python, Backend) From d4950dcb39515a7ee5ed94317aba9ffe4b8ab649 Mon Sep 17 00:00:00 2001 From: RJ786 <119718513+Rejoan-Sardar@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:53:12 +0530 Subject: [PATCH 11/11] Update algorithms.py --- pydatastructs/graphs/algorithms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 92e5f0272..003534551 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -3,7 +3,7 @@ data structure. """ import os -from collections import deques +from collections import deques, queues from concurrent.futures import ThreadPoolExecutor from pydatastructs.utils.misc_util import ( _comp, raise_if_backend_is_not_python, Backend)