Planning a path
Turn the world into a grid of free and blocked squares, then search for the shortest route. Learn breadth-first search and A*, the classic path planners.
In 60 seconds
Planning a path
Turn the world into a grid of free and blocked squares, then search for the shortest route. Learn breadth-first search and A*, the classic path planners.
Breadth-first search (BFS)
from collections import deque
grid = [
"S..#....",
".#.#.##.",
".#...#..",
".####.#.",
"......#G",
]
rows, cols = len(grid), len(grid[0])
start, goal = (0, 0), (4, 7)
dist = {start: 0}
queue = deque([start])
while queue:
r, c = queue.popleft()
for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != "#" and (nr, nc) not in dist:
dist[(nr, nc)] = dist[(r, c)] + 1
queue.append((nr, nc))
print("fewest steps from S to G:", dist.get(goal, "no path"))
print("squares explored:", len(dist))A*: search with a sense of direction
g, the steps taken from the start so far, and h, a guess of the steps still needed to reach the goal. It always explores next the square with the smallest f = g + h.- 1
Start
Put the start square in a priority list with f = h(start). - 2
Pick
Take out the square with the smallest f. - 3
Goal?
If it is the goal, walk back through the saved "came from" links to get the path. - 4
Expand
For each free neighbour, if this route to it is shorter than any found before, save its cost and where it came from, and add it with f = g + h. - 5
Repeat
Until the goal is found, or the list is empty (no path exists).
| Planner | Finds shortest path? | Explores | Notes |
|---|---|---|---|
| BFS | Yes, when every step costs the same | Everything, ring by ring | Simplest correct method |
| Dijkstra's algorithm | Yes, even when steps cost different amounts | In order of cost from the start | For roads with different lengths or speeds |
| A* | Yes, if the guess never overestimates | Mostly towards the goal | The usual choice on maps |
| Greedy "go towards the goal" | No | Very little | Fast but can get stuck behind walls |
# in the top row to .. Does the step count change? Does the number of squares explored change?Watch and read more
Lab
A working A* planner that finds a valid, shortest path on a grid, or says there is none.
The problem
This runs in your browser. The starter already has the grid, neighbours, the Manhattan guess and most of A*.
- Finish TODO 1: when the goal is reached, walk back through
came_fromto build the path, and reverse it so it goes from start to goal. - Finish TODO 2: when you find a cheaper way to a neighbour, save its cost and where it came from, and push it on the heap.
- Run it. The path is drawn with
*. - Add a wall that cuts the grid in two, and check that
astarreturnsNone.
The check tests your planner on several grids: the path must start and end in the right place, move one square at a time, never enter a wall, and be as short as possible.
Practice lab
Edit the code and run it. Python runs in your browser — nothing is sent to our server.
No output. Add print(...) to see values.
You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1What happens to A* if you use h = 3 × Manhattan distance?Reveal
The guess can now overestimate, so A* is no longer guaranteed to find the shortest path. It becomes greedier: it rushes towards the goal and usually explores fewer squares, but it may return a longer route. Some real systems accept this on purpose to plan faster, a trade-off called weighted A*.
Q2Why does the heap store g as well as f?Reveal
So that when a square is taken out, we know the cost of the route that put it there, and can compute the cost to its neighbours. A square can be pushed more than once with different costs; storing g with each entry keeps each route's cost correct. Some versions also skip an entry if a cheaper cost for that square is already known.
Questions people ask
Why does A* need a guess that never overestimates?
If the guess can be too big, A* may think a good route is worse than it really is, and finish with a longer path. A guess that is never too big (called admissible) keeps the shortest path safe. A guess of zero everywhere is also admissible; then A* behaves like Dijkstra's algorithm.
What if the robot can move diagonally?
Then each square has up to eight neighbours, diagonal steps usually cost about 1.41 instead of 1, and you need a different guess, such as the straight-line distance. Many planners also forbid cutting diagonally past the corner of a wall.
Do self-driving cars use grids?
Often in parts. Long routes are usually planned on a road network, like a map app does. Near the car, systems often use grids or other maps of free space to plan exact movements around obstacles.
What if the map is huge?
Grids get very big in 3D or over large areas. Planners then use coarser grids, graphs of important places, or sampling methods that try random points and connect them. The ideas of cost and a good guess still apply.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.