-
Notifications
You must be signed in to change notification settings - Fork 312
Implemented A* algorithm and updated test cases #576
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
Changes from 2 commits
b2a6d2d
47e8368
acf466e
d72bf9d
b1ca7cf
1cfebb7
ecae767
55642c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -11,6 +11,7 @@ | |||||
from pydatastructs.graphs.graph import Graph | ||||||
from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel | ||||||
from pydatastructs import PriorityQueue | ||||||
from pydatastructs.graphs.graph import AdjacencyListGraphNode | ||||||
|
||||||
__all__ = [ | ||||||
'breadth_first_search', | ||||||
|
@@ -24,6 +25,7 @@ | |||||
'topological_sort', | ||||||
'topological_sort_parallel', | ||||||
'max_flow' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comma here. |
||||||
'a_star_with_manhattan' | ||||||
] | ||||||
|
||||||
Stack = Queue = deque | ||||||
|
@@ -700,6 +702,7 @@ 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_with_manhattan' -> A* algorithm with Manhattan distance | ||||||
source: str | ||||||
The name of the source the node. | ||||||
target: str | ||||||
|
@@ -736,16 +739,27 @@ def shortest_paths(graph: Graph, algorithm: str, | |||||
({'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'}) | ||||||
|
||||||
>>> grid_graph = Graph(AdjacencyListGraphNode("0,0"), AdjacencyListGraphNode("1,1")) | ||||||
>>> grid_graph.add_edge('0,0', '1,1', 2) | ||||||
>>> shortest_paths(grid_graph, 'a_star_with_manhattan', '0,0', '1,1') | ||||||
(2, {'1,1': '0,0'}) | ||||||
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)) | ||||||
import pydatastructs.graphs.algorithms as algorithms | ||||||
if algorithm == 'a_star_with_manhattan': | ||||||
# A* with this implementation requires both source and target | ||||||
if not target: | ||||||
raise ValueError("Target must be specified for A* algorithm") | ||||||
|
||||||
func = "_a_star_with_manhattan_" + graph._impl | ||||||
return getattr(algorithms, func)(graph, source, target) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not needed. The logic below already handles this. |
||||||
func = "_" + algorithm + "_" + graph._impl | ||||||
if not hasattr(algorithms, func): | ||||||
raise NotImplementedError( | ||||||
|
@@ -811,6 +825,57 @@ def _dijkstra_adjacency_list(graph: Graph, start: str, target: str): | |||||
|
||||||
_dijkstra_adjacency_matrix = _dijkstra_adjacency_list | ||||||
|
||||||
def _a_star_with_manhattan_adjacency_list(graph: Graph, start: str, target: str, **kwargs): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try changing the body of this function to |
||||||
""" | ||||||
A* algorithm with Manhattan distance as the heuristic function for grid-based graphs. | ||||||
""" | ||||||
def manhattan_distance(node1: str, node2: str) -> float: | ||||||
try: | ||||||
x1, y1 = map(int, node1.split(",")) | ||||||
x2, y2 = map(int, node2.split(",")) | ||||||
return abs(x1 - x2) + abs(y1 - y2) | ||||||
except (ValueError, TypeError): | ||||||
raise ValueError(f"Invalid node format. Expected 'x,y', got {node1} or {node2}") | ||||||
# Validate inputs | ||||||
if start == target: | ||||||
return 0, {start: None} | ||||||
if start not in graph.vertices or target not in graph.vertices: | ||||||
raise ValueError(f"Start or target node not in graph. Start: {start}, Target: {target}") | ||||||
g_score = {v: float('inf') for v in graph.vertices} | ||||||
f_score = {v: float('inf') for v in graph.vertices} | ||||||
pred = {v: None for v in graph.vertices} | ||||||
visited = {v: False for v in graph.vertices} | ||||||
g_score[start] = 0 | ||||||
f_score[start] = manhattan_distance(start, target) | ||||||
pq = PriorityQueue(implementation='binomial_heap') | ||||||
pq.push(start, f_score[start]) | ||||||
while not pq.is_empty: | ||||||
current = pq.pop() | ||||||
if current == target: | ||||||
path_pred = {} | ||||||
node = target | ||||||
while node is not None: | ||||||
path_pred[node] = pred[node] | ||||||
node = pred[node] | ||||||
return g_score[target], path_pred | ||||||
visited[current] = True | ||||||
for neighbor in graph.neighbors(current): | ||||||
if visited[neighbor.name]: | ||||||
continue | ||||||
edge = graph.get_edge(current, neighbor.name) | ||||||
if not edge: | ||||||
continue | ||||||
tentative_g_score = g_score[current] + edge.value | ||||||
if tentative_g_score < g_score[neighbor.name]: | ||||||
pred[neighbor.name] = current | ||||||
g_score[neighbor.name] = tentative_g_score | ||||||
f_score[neighbor.name] = ( | ||||||
tentative_g_score + | ||||||
manhattan_distance(neighbor.name, target) | ||||||
) | ||||||
pq.push(neighbor.name, f_score[neighbor.name]) | ||||||
raise ValueError(f"No path exists between {start} and {target}") | ||||||
_a_star_with_manhattan_adjacency_matrix = _a_star_with_manhattan_adjacency_list | ||||||
def all_pair_shortest_paths(graph: Graph, algorithm: str, | ||||||
**kwargs) -> tuple: | ||||||
""" | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -4,7 +4,6 @@ | |||||||
depth_first_search, shortest_paths, topological_sort, | ||||||||
topological_sort_parallel, max_flow) | ||||||||
from pydatastructs.utils.raises_util import raises | ||||||||
|
||||||||
def test_breadth_first_search(): | ||||||||
|
||||||||
def _test_breadth_first_search(ds): | ||||||||
|
@@ -293,7 +292,34 @@ def _test_shortest_paths_positive_edges(ds, algorithm): | |||||||
graph.remove_edge('SLC', 'D') | ||||||||
graph.add_edge('D', 'SLC', -10) | ||||||||
assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC')) | ||||||||
|
||||||||
def _test_a_star_manhattan(ds): | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should i remove this tests for the astar algorithem here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This suggestion means that you need to add a blank line before the function definition starts. |
||||||||
import pydatastructs.utils.misc_util as utils | ||||||||
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") | ||||||||
vertices = [ | ||||||||
GraphNode("0,0"), | ||||||||
GraphNode("1,1"), | ||||||||
GraphNode("2,2") | ||||||||
] | ||||||||
graph = Graph(*vertices) | ||||||||
graph.add_edge("0,0", "1,1", 2) | ||||||||
graph.add_edge("1,1", "2,2", 3) | ||||||||
distance, pred = shortest_paths(graph, 'a_star_with_manhattan', "0,0", "2,2") | ||||||||
assert distance == 5 # 2 + 3 | ||||||||
assert pred['2,2'] == '1,1' | ||||||||
assert pred['1,1'] == '0,0' | ||||||||
# No path scenario | ||||||||
no_path_graph = Graph( | ||||||||
GraphNode("0,0"), | ||||||||
GraphNode("1,1"), | ||||||||
GraphNode("2,2") | ||||||||
) | ||||||||
with raises(ValueError, match="No path exists"): | ||||||||
shortest_paths(no_path_graph, 'a_star_with_manhattan', "0,0", "2,2") | ||||||||
# Same node scenario | ||||||||
same_node_graph = Graph(GraphNode("1,1")) | ||||||||
distance, pred = shortest_paths(same_node_graph, 'a_star_with_manhattan', "1,1", "1,1") | ||||||||
assert distance == 0 | ||||||||
assert pred == {'1,1': None} | ||||||||
def _test_shortest_paths_negative_edges(ds, algorithm): | ||||||||
import pydatastructs.utils.misc_util as utils | ||||||||
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") | ||||||||
|
@@ -321,6 +347,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_a_star_manhattan("List") | ||||||||
_test_a_star_manhattan("Matrix") | ||||||||
|
||||||||
def test_all_pair_shortest_paths(): | ||||||||
|
||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that you have added this import, but it is being used in a code block that is commented. Please try removing this and check if the CI tests pass.