Skip to content

Commit 59e9dca

Browse files
committed
feat(visualizations): add binary matrix shortest path algorithm visualization
- Create interactive 8-directional BFS visualization for shortest path - Display grid with blocked/clear cells, start (S), and goal (G) markers - Show BFS queue with cell coordinates and distances - Highlight path cells when goal is reached - Add race condition fix with isProcessing guard for animation safety - Add robust grid parsing to handle trailing semicolons/spaces - Add to landing page under Matrix section Signed-off-by: Ayush Joshi <ayush854032@gmail.com>
1 parent 65e8460 commit 59e9dca

6 files changed

Lines changed: 807 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Given an `n x n` binary matrix grid, return the length of the shortest clear path in
2+
the matrix. If there is no clear path, return -1.
3+
4+
A clear path in a binary matrix is a path from the top-left cell (i.e., (0, 0)) to the
5+
bottom-right cell (i.e., (n - 1, n - 1)) such that:
6+
7+
- All the visited cells of the path are 0.
8+
- All the adjacent cells of the path are 8-directionally connected (i.e., they are
9+
different and they share an edge or a corner).
10+
11+
The length of a clear path is the number of visited cells of this path.
12+
"""
13+
14+
from collections import deque
15+
from typing import List
16+
17+
18+
class Solution:
19+
def _checkIfInBounds(self, i: int, j: int, rows: int, cols: int) -> bool:
20+
return 0 <= i < rows and 0 <= j < cols
21+
22+
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
23+
rows = len(grid)
24+
cols = len(grid[0])
25+
26+
if not grid[0][0] == 0 or not grid[rows - 1][cols - 1] == 0:
27+
return -1
28+
29+
visited = [[False for _ in range(cols)] for _ in range(rows)]
30+
directions = [
31+
(-1, 0),
32+
(-1, +1),
33+
(0, +1),
34+
(+1, +1),
35+
(+1, 0),
36+
(+1, -1),
37+
(0, -1),
38+
(-1, -1),
39+
]
40+
41+
shortest_path = float("inf")
42+
queue = deque([(0, 0, 1)])
43+
while len(queue) > 0:
44+
curr_i, curr_j, distance = queue.popleft()
45+
if (
46+
self._checkIfInBounds(curr_i, curr_j, rows, cols)
47+
and grid[curr_i][curr_j] == 0
48+
and not visited[curr_i][curr_j]
49+
):
50+
visited[curr_i][curr_j] = True
51+
if curr_i == rows - 1 and curr_j == cols - 1:
52+
shortest_path = min(distance, shortest_path)
53+
for di, dj in directions:
54+
new_i = curr_i + di
55+
new_j = curr_j + dj
56+
queue.append((new_i, new_j, distance + 1))
57+
58+
return shortest_path if not shortest_path == float("inf") else -1
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
3+
from algos.matrix.binary_matrix_shortest_path import Solution
4+
5+
6+
@pytest.fixture
7+
def solution() -> Solution:
8+
return Solution()
9+
10+
11+
def test_binary_matrix_shortest_path(solution: Solution) -> None:
12+
assert solution.shortestPathBinaryMatrix(grid=[[0, 1], [1, 0]]) == 2
13+
assert (
14+
solution.shortestPathBinaryMatrix(grid=[[0, 0, 0], [1, 1, 0], [1, 1, 0]]) == 4
15+
)
16+
assert (
17+
solution.shortestPathBinaryMatrix(grid=[[1, 0, 0], [1, 1, 0], [1, 1, 0]]) == -1
18+
)
19+
assert (
20+
solution.shortestPathBinaryMatrix(
21+
grid=[[0, 0, 1, 0], [1, 0, 1, 0], [1, 1, 0, 1], [0, 0, 0, 0]]
22+
)
23+
== 4
24+
)
25+
assert (
26+
solution.shortestPathBinaryMatrix(
27+
grid=[
28+
[0, 0, 0, 0, 1],
29+
[1, 0, 0, 0, 0],
30+
[0, 1, 0, 1, 0],
31+
[0, 0, 0, 1, 1],
32+
[0, 0, 0, 1, 0],
33+
]
34+
)
35+
== -1
36+
)

visualizations/index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ <h3>Flood Fill</h3>
176176
<p>Fill connected pixels with a new color starting from a point.</p>
177177
<span class="algo-tag">BFS</span>
178178
</a>
179+
<a href="matrix/binary-matrix-shortest-path/index.html" class="algo-card">
180+
<h3>Binary Matrix Shortest Path</h3>
181+
<p>Find the shortest clear path in a binary matrix using 8-directional BFS.</p>
182+
<span class="algo-tag">BFS</span>
183+
</a>
179184
</div>
180185
</div>
181186
</div>
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Binary Matrix Shortest Path - BFS</title>
7+
<link rel="stylesheet" href="../../shared/css/main.css">
8+
<link rel="stylesheet" href="style.css">
9+
</head>
10+
<body>
11+
<div class="container">
12+
<a href="../../index.html" class="back-link">&larr; Back to all algorithms</a>
13+
<h1>Binary Matrix Shortest Path</h1>
14+
<p class="description">Find the shortest clear path from top-left to bottom-right using 8-directional BFS</p>
15+
16+
<div class="controls">
17+
<div class="input-group">
18+
<label>Grid (rows separated by ;):</label>
19+
<input type="text" id="gridInput" value="0,0,0;1,1,0;1,1,0" placeholder="0,0,0;1,1,0;1,1,0" style="width: 200px;">
20+
</div>
21+
<button class="btn-random" id="randomBtn">Random</button>
22+
<button class="btn-primary" id="resetBtn">Reset</button>
23+
<button class="btn-secondary" id="stepBtn">Step</button>
24+
<button class="btn-success" id="playBtn">Play</button>
25+
<div class="speed-control">
26+
<label>Speed:</label>
27+
<input type="range" id="speedSlider" min="100" max="2000" value="500">
28+
</div>
29+
</div>
30+
31+
<div class="main-content">
32+
<div class="visualization">
33+
<div class="section-title">Binary Matrix</div>
34+
<div class="grid-container" id="gridContainer"></div>
35+
36+
<div class="info-panel">
37+
<div class="info-box">
38+
<h3>Current Cell</h3>
39+
<div class="info-value" id="currentCell">-</div>
40+
</div>
41+
<div class="info-box">
42+
<h3>Distance</h3>
43+
<div class="info-value" id="currentDistance">-</div>
44+
</div>
45+
<div class="info-box">
46+
<h3>Queue Size</h3>
47+
<div class="info-value" id="queueSize">0</div>
48+
</div>
49+
<div class="info-box result-box">
50+
<h3>Shortest Path</h3>
51+
<div class="info-value" id="shortestPath">-</div>
52+
</div>
53+
</div>
54+
55+
<div class="queue-display">
56+
<div class="section-title">BFS Queue</div>
57+
<div class="queue" id="queueDisplay">
58+
<span class="empty-text">Empty</span>
59+
</div>
60+
</div>
61+
62+
<div class="status" id="status">
63+
Click "Step" or "Play" to find the shortest path from (0,0) to (n-1,n-1)
64+
</div>
65+
66+
<div class="legend">
67+
<div class="legend-item">
68+
<div class="legend-color blocked-legend"></div>
69+
<span>Blocked (1)</span>
70+
</div>
71+
<div class="legend-item">
72+
<div class="legend-color clear-legend"></div>
73+
<span>Clear (0)</span>
74+
</div>
75+
<div class="legend-item">
76+
<div class="legend-color start-legend"></div>
77+
<span>Start</span>
78+
</div>
79+
<div class="legend-item">
80+
<div class="legend-color goal-legend"></div>
81+
<span>Goal</span>
82+
</div>
83+
<div class="legend-item">
84+
<div class="legend-color current-legend"></div>
85+
<span>Current</span>
86+
</div>
87+
<div class="legend-item">
88+
<div class="legend-color visited-legend"></div>
89+
<span>Visited</span>
90+
</div>
91+
<div class="legend-item">
92+
<div class="legend-color path-legend"></div>
93+
<span>Path</span>
94+
</div>
95+
</div>
96+
</div>
97+
98+
<div class="code-panel">
99+
<div class="section-title">Python Code - BFS Approach</div>
100+
<div class="code-line" id="code-0">
101+
<span class="code-keyword">if</span> grid[<span class="code-number">0</span>][<span class="code-number">0</span>] != <span class="code-number">0</span> <span class="code-keyword">or</span> grid[n-<span class="code-number">1</span>][n-<span class="code-number">1</span>] != <span class="code-number">0</span>:
102+
</div>
103+
<div class="code-line" id="code-1">
104+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">return</span> -<span class="code-number">1</span>
105+
</div>
106+
<div class="code-line" id="code-2">
107+
<span class="code-variable">queue</span> = deque([(<span class="code-number">0</span>, <span class="code-number">0</span>, <span class="code-number">1</span>)]) <span class="code-comment"># (row, col, distance)</span>
108+
</div>
109+
<div class="code-line" id="code-3">
110+
<span class="code-keyword">while</span> <span class="code-function">len</span>(queue) > <span class="code-number">0</span>:
111+
</div>
112+
<div class="code-line" id="code-4">
113+
&nbsp;&nbsp;&nbsp;&nbsp;i, j, dist = queue.<span class="code-function">popleft</span>()
114+
</div>
115+
<div class="code-line" id="code-5">
116+
&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if</span> in_bounds <span class="code-keyword">and</span> grid[i][j] == <span class="code-number">0</span> <span class="code-keyword">and not</span> visited[i][j]:
117+
</div>
118+
<div class="code-line" id="code-6">
119+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;visited[i][j] = <span class="code-keyword">True</span>
120+
</div>
121+
<div class="code-line" id="code-7">
122+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">if</span> i == n-<span class="code-number">1</span> <span class="code-keyword">and</span> j == n-<span class="code-number">1</span>: <span class="code-keyword">return</span> dist
123+
</div>
124+
<div class="code-line" id="code-8">
125+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="code-keyword">for</span> di, dj <span class="code-keyword">in</span> directions: <span class="code-comment"># 8 directions</span>
126+
</div>
127+
<div class="code-line" id="code-9">
128+
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;queue.<span class="code-function">append</span>((i+di, j+dj, dist+<span class="code-number">1</span>))
129+
</div>
130+
<div class="code-line" id="code-10">
131+
<span class="code-keyword">return</span> -<span class="code-number">1</span> <span class="code-comment"># no path found</span>
132+
</div>
133+
</div>
134+
</div>
135+
</div>
136+
137+
<script src="../../shared/js/visualizer.js"></script>
138+
<script src="script.js"></script>
139+
</body>
140+
</html>

0 commit comments

Comments
 (0)