Skip to content

Stop bidirectional A* when either frontier is empty - #15484

Merged
cclauss merged 2 commits into
TheAlgorithms:masterfrom
dhairyajangir:fix/bidirectional-empty-frontier
Oct 3, 2026
Merged

cclauss merged 2 commits into
TheAlgorithms:masterfrom
dhairyajangir:fix/bidirectional-empty-frontier

Conversation

@dhairyajangir

@dhairyajangir dhairyajangir commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Describe your change

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Add or change doctests? -- Appended at the maintainer's explicit request; existing examples are unchanged.
  • Documentation change?

Stop the bidirectional A* loop when either frontier is empty. The previous or condition allowed another iteration with one empty list, although that iteration unconditionally pops from both frontiers.

For example, with grid = [[0, 1, 0], [1, 1, 0]], start (0, 0), and goal (0, 2), the forward frontier empties first and the original raises IndexError: pop from empty list. The fixed guard returns the existing start-only no-path result. The mirrored backward-frontier case is also covered. Node matching, heuristics, and the return convention are unchanged.

At the maintainer's request, follow-up 74af1de3d89d81cbea6ade9c7000947e4aabef21 appends nine doctest examples while preserving all 18 originals. It adds an optional --benchmark block in this same file; the production methods and existing demonstration are unchanged by the follow-up.

Validation on free-threaded CPython 3.15.0rc2, GIL disabled:

  • Original algorithm with the appended tests: 25 passed, two failed, both exposing IndexError when one frontier empties first. Fixed algorithm: 27 passed. Controls cover both frontiers emptying together and a reachable corridor. The global grid is restored afterward.
  • pre-commit run --all-files --show-diff-on-failure: exit 0, all applicable configured hooks passed without modifying files. git diff --check passed.
  • Independent review reproduced the RED/GREEN result, confirmed every original example and production method was preserved, and checked both benchmark paths.

Run python graphs/bidirectional_a_star.py --benchmark for the optional timeit benchmark. It uses 2,025 total grid cells but only 89 traversable cells, forming a deterministic staircase path. Grid setup and an exact-path assertion are outside timing; each iteration creates a fresh search object. The original/fixed comparison restores only the original or guard in a temporary copy, retaining identical benchmark code and inputs. Both versions return the expected 89-cell path. Unreachable baseline cases crash and are tested separately, not included in timing.

Five samples of 100 searches each, pinned to CPU 0 at the same nice level, measured milliseconds per search:

Version Best Median Sample range
Original or 2.244 2.321 2.244–2.369
Fixed and 2.291 2.606 2.291–4.031

The best sample was about 2.1% slower and the median was 1.123× the original, with substantial variance. These are measurements of this specific reachable workload, not an intrinsic guard-overhead estimate, a speedup claim, or 2,025 expanded nodes.

AI assistance was used to prepare this change and follow-up; a separate agent independently reviewed and reproduced the regression evidence.

Checklist

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues, then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

No new file or algorithm is added, and no issue is claimed. All 18 original doctests are preserved and nine examples are appended in response to review. The existing class doctests pass; no new functions are introduced. The personal-authorship attestation remains unchecked.

@algorithms-keeper algorithms-keeper Bot added awaiting reviews This PR is ready to be reviewed enhancement This PR modified some existing files labels Oct 2, 2026
@cclauss

cclauss commented Oct 3, 2026

Copy link
Copy Markdown
Member

Please add one or more tests (without removing or modifying any existing tests) that fail with the current algorithm but pass with the proposed algorithm.

Please add a timeit or similar benchmark that measures the performance difference on a large grid (like 2k items).

Preserve all 18 existing doctest examples and append frontier exhaustion regressions and controls. Add an optional 2,025-cell benchmark with a documented 89-cell reachable corridor, leaving the production fix unchanged.
@dhairyajangir

Copy link
Copy Markdown
Contributor Author

Addressed in 74af1de3d89d81cbea6ade9c7000947e4aabef21.

  • Preserved all 18 existing doctest examples and appended nine. With the original or guard, two new assertions fail with IndexError (each frontier emptying first); with the fix, all 27 examples pass. Controls cover both frontiers emptying together and a reachable corridor.
  • Added python graphs/bidirectional_a_star.py --benchmark using a 45×45 grid: 2,025 cells, 89 traversable, with a verified 89-cell staircase path. Input construction and the path assertion are outside timing; each iteration creates a fresh search object. Original/fixed exports have identical benchmark code and inputs. The unreachable baseline crashes are covered by the regressions, separately from the common reachable timing workload.

On free-threaded CPython 3.15.0rc2, five samples of 100 searches each measured:

Milliseconds per search Original Fixed
Best 2.244 2.291
Median 2.321 2.606
Range 2.244–2.369 2.291–4.031

The best sample was about 2.1% slower; the median was 1.123× the original, with substantial variance. I would not attribute that spread entirely to the guard or generalize it to arbitrary grids.

All configured pre-commit hooks passed. The production fix remains unchanged, and independent review verified the preserved examples, regression outcomes, and benchmark setup. Codex assisted with this follow-up.

@cclauss cclauss added the hacktoberfest-accepted Accepted to be counted towards Hacktoberfest label Oct 3, 2026
@algorithms-keeper algorithms-keeper Bot removed the awaiting reviews This PR is ready to be reviewed label Oct 3, 2026
@cclauss
cclauss merged commit 29f9da7 into TheAlgorithms:master Oct 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement This PR modified some existing files hacktoberfest-accepted Accepted to be counted towards Hacktoberfest

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants