11"""
22https://en.wikipedia.org/wiki/Bidirectional_search
3+
4+ Run this file with --benchmark to time a search on a 45 by 45 grid.
5+ The grid has 2,025 cells, with a single 89-cell staircase corridor.
36"""
47
58from __future__ import annotations
@@ -179,6 +182,25 @@ class BidirectionalAStar:
179182 >>> bd_astar.search() # doctest: +NORMALIZE_WHITESPACE
180183 [(0, 0), (0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 4),
181184 (2, 5), (3, 5), (4, 5), (5, 5), (5, 6), (6, 6)]
185+
186+ An isolated endpoint exhausts one frontier before the other.
187+ >>> original_grid = grid[:]
188+ >>> grid[:] = [[0, 1, 0], [1, 1, 0]]
189+ >>> BidirectionalAStar((0, 0), (0, 2)).search()
190+ [(0, 0)]
191+ >>> BidirectionalAStar((0, 2), (0, 0)).search()
192+ [(0, 2)]
193+
194+ Both frontiers can also empty together, without a path.
195+ >>> grid[:] = [[0, 1, 0]]
196+ >>> BidirectionalAStar((0, 0), (0, 2)).search()
197+ [(0, 0)]
198+
199+ A reachable corridor still returns the complete path.
200+ >>> grid[:] = [[0, 0, 0]]
201+ >>> BidirectionalAStar((0, 0), (0, 2)).search()
202+ [(0, 0), (0, 1), (0, 2)]
203+ >>> grid[:] = original_grid
182204 """
183205
184206 def __init__ (self , start : TPosition , goal : TPosition ) -> None :
@@ -187,7 +209,7 @@ def __init__(self, start: TPosition, goal: TPosition) -> None:
187209 self .reached = False
188210
189211 def search (self ) -> list [TPosition ]:
190- while self .fwd_astar .open_nodes or self .bwd_astar .open_nodes :
212+ while self .fwd_astar .open_nodes and self .bwd_astar .open_nodes :
191213 self .fwd_astar .open_nodes .sort ()
192214 self .bwd_astar .open_nodes .sort ()
193215 current_fwd_node = self .fwd_astar .open_nodes .pop (0 )
@@ -257,3 +279,33 @@ def retrace_bidirectional_path(
257279 bidir_astar = BidirectionalAStar (init , goal )
258280 bd_end_time = time .time () - bd_start_time
259281 print (f"BidirectionalAStar execution time = { bd_end_time :f} seconds" )
282+
283+ import sys
284+
285+ if "--benchmark" in sys .argv :
286+ from timeit import repeat
287+
288+ # A single staircase corridor gives both versions the same reachable path.
289+ # Keep grid construction and the correctness check outside the timing.
290+ size = 45
291+ grid = [[1 ] * size for _ in range (size )]
292+ expected_path = []
293+ for offset in range (size ):
294+ grid [offset ][offset ] = 0
295+ expected_path .append ((offset , offset ))
296+ if offset < size - 1 :
297+ grid [offset ][offset + 1 ] = 0
298+ expected_path .append ((offset , offset + 1 ))
299+ init , goal = (0 , 0 ), (size - 1 , size - 1 )
300+ assert BidirectionalAStar (init , goal ).search () == expected_path
301+
302+ searches = 100
303+ samples = repeat (
304+ "BidirectionalAStar(init, goal).search()" ,
305+ globals = globals (),
306+ number = searches ,
307+ repeat = 5 ,
308+ )
309+ print (f"Benchmark: { size * size } cells, { len (expected_path )} traversable" )
310+ print (f"Seconds per search ({ searches } searches per sample):" )
311+ print ([sample / searches for sample in samples ])
0 commit comments