Skip to main content

A clean, dependency-free Python library for BFS, DFS, and grid traversal algorithms

Project description

๐Ÿ” Graph & Grid Algorithm Library

MIT License Python No dependencies

A clean, dependency-free Python library for common BFS, DFS, and grid traversal algorithms. Designed as a reliable toolkit for competitive programming, interview prep, and educational use.


๐Ÿ“ฅ Installation

pip install graph-algs

Then import directly from the top-level package โ€” no need to reference internal subfolders:

from graph_lib import shortest_path, bfs, lee_algorithm, has_cycle_directed

๐Ÿ“ฆ Modules

graph_algorithms/
โ”œโ”€โ”€ bfs/
โ”‚   โ”œโ”€โ”€ bfs_general_lib.py      # BFS traversal and shortest paths
โ”‚   โ”œโ”€โ”€ bfs_undirected_lib.py   # Connectivity for undirected graphs
โ”‚   โ”œโ”€โ”€ bfs_directed_lib.py     # Weak connectivity for directed graphs
โ”‚   โ””โ”€โ”€ grid_bfs_lib.py         # Grid BFS โ€” islands, pathfinding, distance problems
โ””โ”€โ”€ dfs/
    โ”œโ”€โ”€ dfs_general_lib.py      # DFS traversal
    โ”œโ”€โ”€ dfs_undirected_lib.py   # Cycle detection for undirected graphs
    โ””โ”€โ”€ dfs_directed_lib.py     # Cycle detection for directed graphs

โš™๏ธ Implementation Conventions

Understanding these conventions is essential for using the library correctly.

Error Handling โ€” Exceptions

All functions raise exceptions on invalid or degenerate input rather than returning a sentinel value:

  • ValueError โ€” empty graph or grid ({}, []), invalid board size (n <= 0)
  • KeyError โ€” start/end node not present in the graph, out-of-bounds grid coordinates, start or end cell being a wall
bfs({}, 'A')                        # โ†’ ValueError: Graph is empty!
bfs(graph, 'Z')                     # โ†’ KeyError: Vertex Z not in graph!
lee_algorithm(grid, (0,0), (9,9))   # โ†’ KeyError: (0, 0) or (9, 9) cell not in grid!
count_islands([])                   # โ†’ ValueError: Grid is empty!

No Path / No Result โ€” None

When inputs are structurally valid but the goal is unreachable, functions return None:

shortest_path(graph, 'A', 'Z')       # โ†’ None  (Z unreachable from A)
lee_algorithm(grid, (0,0), (3,3))    # โ†’ None  (blocked)
rotting_oranges(grid)                # โ†’ None  (some fresh oranges can never rot)

Input Validation Scope

The library performs only semantic validation โ€” it checks whether inputs make sense for the algorithm, not whether they conform to a type contract.

โœ… Checked:

  • Whether the graph or grid is empty
  • Whether start/end nodes exist in the graph
  • Whether grid coordinates are in bounds
  • Whether start/end cells are traversable (value 0)
  • Whether board size is positive (knight moves)

โŒ Not checked:

  • Whether graph is actually a dict
  • Whether grid rows are all the same length
  • Whether node values are hashable
  • Whether start/end are tuples vs. lists in grid functions

If you pass malformed data (e.g., a list instead of a dict, or a grid with jagged rows), you will get a native Python exception.


๐Ÿ“– API Reference

bfs/bfs_general_lib.py

Basic Traversal

bfs(graph, start, visited=None) โ†’ set

Returns the set of all nodes reachable from start. Accepts an optional visited set to continue a traversal across multiple calls.

bfs_order(graph, start, visited=None) โ†’ list

Returns nodes in BFS visit order. Also accepts an optional shared visited set.

bfs_count(graph, start, visited=None) โ†’ int

Returns the count of nodes reachable from start.

Shortest Paths

shortest_path(graph, start, end) โ†’ list | None

Returns the shortest path as a node list. Returns [start] if start == end. Returns None if no path exists.

shortest_path_len(graph, start, end) โ†’ int | None

Returns the length (number of edges) of the shortest path. Returns 0 if start == end. Returns None if no path exists.


bfs/bfs_undirected_lib.py

is_connected_undirected(graph) โ†’ bool

Returns True if the undirected graph is connected.

count_components_undirected(graph) โ†’ int

Returns the number of connected components.

get_components_order_undirected(graph) โ†’ list[list]

Returns a list of components, each as an ordered BFS traversal list.

largest_component_size_undirected(graph) โ†’ int

Returns the size of the largest component.

shortest_path_len_undirected(graph, start, end) โ†’ int | None

Faster shortest path length using bidirectional BFS. Requires both start and end to be present in the graph. Returns None if no path exists.


bfs/bfs_directed_lib.py

Connectivity functions for directed graphs use weak connectivity โ€” the graph is normalised to undirected via normalize() before traversal.

normalize(graph) โ†’ dict

Converts a directed graph into an undirected one by adding reverse edges. Used internally; safe to call directly.

is_connected_directed(graph) โ†’ bool

Returns True if the directed graph is weakly connected.

count_components_directed(graph) โ†’ int

Returns the number of weakly connected components.

get_components_order_directed(graph) โ†’ list[list]

Returns a list of weakly connected components, each as an ordered BFS traversal list.

largest_component_size_directed(graph) โ†’ int

Returns the size of the largest weakly connected component.


dfs/dfs_general_lib.py

dfs(graph, start, visited=None) โ†’ set

Recursive DFS. Returns the set of all visited nodes. Accepts an optional shared visited set.

dfs_order(graph, start) โ†’ list

Iterative DFS using an explicit stack. Returns nodes in DFS visit order. Does not accept a shared visited set โ€” always starts fresh.


dfs/dfs_undirected_lib.py

has_cycle_undirected(graph) โ†’ bool

Detects cycles in an undirected graph using parent tracking.


dfs/dfs_directed_lib.py

has_cycle_directed(graph) โ†’ bool

Detects cycles in a directed graph using the white/gray/black coloring method.


bfs/grid_bfs_lib.py

Grid convention: 0 = traversable cell, 1 = wall/obstacle (except dist_to_nearest_one and rotting_oranges which use domain-specific values).

Island Problems

count_islands(grid) โ†’ int

Counts the number of connected regions of 0-cells (4-directional).

get_islands_order(grid) โ†’ list[list[tuple]]

Returns all islands as lists of (row, col) coordinates in BFS order.

largest_island_area(grid) โ†’ int

Returns the cell count of the largest island.

Pathfinding (Lee Algorithm)

lee_algorithm(grid, start, end) โ†’ list[tuple] | None

Finds the shortest path in a grid from start to end. Returns path as a list of (row, col) tuples. Returns None if unreachable.

lee_algorithm_len(grid, start, end) โ†’ int | None

Returns the length of the shortest grid path. Returns 0 if start == end. Returns None if unreachable.

Distance & Simulation

dist_to_nearest_one(grid) โ†’ list[list[int]]

Multi-source BFS. Returns a grid where each cell contains its distance to the nearest 1-cell. Cells that are 1 have distance 0.

rotting_oranges(grid) โ†’ int | None

Simulates rotting spread (LeetCode 994). Grid values: 0 = empty, 1 = fresh orange, 2 = rotten orange. Returns minutes until all oranges rot, or None if impossible.

Knight Moves

knight_moves(n, start, end) โ†’ list[tuple] | None

Finds the shortest path for a chess knight on an nร—n board. Returns path as (row, col) tuples. Returns None if unreachable.

knight_moves_len(n, start, end) โ†’ int | None

Returns the minimum number of moves for a knight to travel from start to end. Returns None if unreachable.


๐Ÿ—บ๏ธ Graph Format

All graph functions expect an adjacency list as a Python dict.

Implicit sink nodes in directed graphs

In a directed graph, nodes that have no outgoing edges don't have to appear as explicit keys โ€” they can exist only as values in other nodes' neighbour lists. This is supported via graph.get(node, []) instead of graph[node] everywhere neighbours are iterated, so a missing key is safely treated as having no outgoing edges.

# โœ… Both forms are equivalent and accepted

# Explicit โ€” sink nodes listed with empty neighbour lists
graph = {
    'A': ['B', 'C'],
    'B': ['D'],
    'C': [],       # sink, explicitly listed
    'D': []        # sink, explicitly listed
}

# Compact โ€” sink nodes omitted as keys entirely
graph = {
    'A': ['B', 'C'],
    'B': ['D'],
    # C and D are implied by appearing in neighbour lists
}

โš ๏ธ Exception: functions that validate start by checking if start not in graph will raise KeyError if a sink node is passed as start and is not an explicit key. If you need to start traversal from a sink, include it explicitly with an empty list.

For undirected graphs, all edges must still be listed in both directions:

graph = {
    1: [2, 3],
    2: [1, 4],
    3: [1],
    4: [2]
}

Nodes can be any hashable type (strings, integers, tuples, etc.).

๐Ÿ—บ๏ธ Grid Format

Grid functions expect a 2D list of integers:

grid = [
    [0, 0, 1, 0],
    [1, 0, 0, 0],
    [0, 0, 1, 1],
    [0, 0, 0, 0],
]

Coordinates are always (row, col). All movement is 4-directional (up, down, left, right).


๐Ÿ’ก Usage Examples

from graph_lib import (
    shortest_path, count_components_undirected, count_components_directed,
    dfs_order, has_cycle_directed, has_cycle_undirected,
    lee_algorithm, rotting_oranges
)

# Shortest path in an undirected graph
graph = {1: [2, 3], 2: [1, 4], 3: [1], 4: [2]}
print(shortest_path(graph, 1, 4))  # โ†’ [1, 2, 4]

# Count weakly connected components in a directed graph
dgraph = {'A': ['B'], 'B': [], 'C': ['D'], 'D': []}
print(count_components_directed(dgraph))  # โ†’ 2

# Count connected components in an undirected graph
ugraph = {1: [2], 2: [1], 3: [4], 4: [3]}
print(count_components_undirected(ugraph))  # โ†’ 2

# Cycle detection
print(has_cycle_directed({'A': ['B'], 'B': ['A']}))        # โ†’ True
print(has_cycle_undirected({1: [2], 2: [1, 3], 3: [2]}))  # โ†’ False

# Grid shortest path
grid = [[0, 0, 0], [1, 1, 0], [0, 0, 0]]
print(lee_algorithm(grid, (0, 0), (2, 2)))  # โ†’ [(0,0), (0,1), (0,2), (1,2), (2,2)]

# Rotting oranges
grid = [[2, 1, 1], [1, 1, 0], [0, 1, 1]]
print(rotting_oranges(grid))  # โ†’ 4

๐Ÿ“Œ Quick Reference: Return Values

Situation Return Value
Invalid / empty input ValueError or KeyError
Valid input, goal unreachable None
start == end (path functions) [start] or 0
Success Result value or list

Made with โค๏ธ and Python

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

graph_algs-0.3.2.tar.gz (10.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graph_algs-0.3.2-py3-none-any.whl (12.3 kB view details)

Uploaded Python 3

File details

Details for the file graph_algs-0.3.2.tar.gz.

File metadata

  • Download URL: graph_algs-0.3.2.tar.gz
  • Upload date:
  • Size: 10.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for graph_algs-0.3.2.tar.gz
Algorithm Hash digest
SHA256 08f92fb449665c6d3b26cb74c46397dd3b0392c784b6f73fa807ed26ea991d96
MD5 570bcc708a840194bcb02954d92b3fc3
BLAKE2b-256 d77488c3aeb330fd44b257c9a4a27af2b95e8068ce1ff265735439775d5d7f60

See more details on using hashes here.

File details

Details for the file graph_algs-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: graph_algs-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 12.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for graph_algs-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ffa2487d2022fa8ff24a4b47aaf05bf1b4d9841dce9fa2af51329236e328b841
MD5 fef5faebddd007fb76abd96045b017a5
BLAKE2b-256 951e560a50d918a9f9d3baf46d7064a1df5c5c451eb114e9ddff2dd19eece0da

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page