# Find Best Path in 2D Grid with Obstacles - A* Search Algorithm
# Description: Finds the shortest path on a 2D grid (with obstacles) using the A* search algorithm.
import heapq
def manhattan_distance(point_a, point_b):
"""
Calculate the Manhattan distance between two points.
Args:
point_a (tuple): First point (row, col)
point_b (tuple): Second point (row, col)
Returns:
int: Manhattan distance between the points
"""
return abs(point_a[0] - point_b[0]) + abs(point_a[1] - point_b[1])
def is_valid_move(grid, point):
"""
Check if a point is a valid move on the grid.
Args:
grid (list of list): 2D grid where 1 represents obstacles
point (tuple): Point to check (row, col)
Returns:
bool: True if the point is a valid move, False otherwise
"""
row, col = point
return (0 <= row < len(grid) and
0 <= col < len(grid[0]) and
grid[row][col] != 1)
def reconstruct_path(came_from, current):
"""
Reconstruct the path from start to goal.
Args:
came_from (dict): Dictionary tracking the previous point for each point
current (tuple): Goal point
Returns:
list: Path from start to goal
"""
path = [current]
while current in came_from:
current = came_from[current]
path.append(current)
return path[::-1]
def a_star(start, goal, grid):
"""
Find the shortest path in a 2D grid using A* search algorithm.
Args:
start (tuple): Starting point (row, col)
goal (tuple): Goal point (row, col)
grid (list of list): 2D grid where 1 represents obstacles
Returns:
list or str: Path from start to goal, or "No path found"
Example:
grid = [
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
[0, 1, 0, 0]
]
start = (0, 0)
goal = (3, 3)
path = a_star(start, goal, grid)
"""
# Possible movement directions: right, down, left, up
DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)]
# Priority queue to store nodes to explore
# Format: (f_score, g_score, point)
open_set = []
heapq.heappush(open_set, (manhattan_distance(start, goal), 0, start))
# Track the path and movement costs
came_from = {}
g_score = {start: 0}
while open_set:
# Get the node with the lowest f_score
_, current_cost, current = heapq.heappop(open_set)
# Check if we've reached the goal
if current == goal:
return reconstruct_path(came_from, current)
# Explore neighboring cells
for dx, dy in DIRECTIONS:
neighbor = (current[0] + dx, current[1] + dy)
# Skip invalid moves
if not is_valid_move(grid, neighbor):
continue
# Calculate tentative movement cost
tentative_cost = current_cost + 1
# Update if this is a better path
if neighbor not in g_score or tentative_cost < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_cost
# Calculate f_score (total estimated cost)
f_score = tentative_cost + manhattan_distance(neighbor, goal)
heapq.heappush(open_set, (f_score, tentative_cost, neighbor))
# No path found
return "No path found"