from queue import Queue
from itertools import count
from typing import TypeVar, Generic, Optional
from collections.abc import Mapping
from dataclasses import dataclass, field

T = TypeVar('T')
VertexId = int
Adjacencies = Mapping[VertexId, VertexId]

@dataclass
class Vertex(Generic[T]):
    value: T
    id: VertexId = field(default_factory=count().__next__)

Vertices = Mapping[VertexId, Vertex]

def breadth_first_search(root_id: VertexId, target_id: VertexId, graph: Adjacencies, vertices: Vertices) -> Optional[Vertex]:
    """Search for TARGET within GRAPH using breadth-first search."""
    queue = Queue()
    queue.put(root_id)
    visited = set()

    while not queue.empty():
        current_id = queue.get()

        if current_id == target_id:
            return vertices[current_id]

        if current_id in visited:
            continue
        visited.add(current_id)

        if current_id in adjacencies:
            for neighbor_id in [id for id in adjacencies[current_id]]:
                queue.put(neighbor_id)

    return None

a = Vertex(value='a')
b = Vertex(value='b')
c = Vertex(value='c')
d = Vertex(value='d')
e = Vertex(value='e')
f = Vertex(value='f')
adjacencies = {
    a.id: {b.id, c.id},
    c.id: {d.id},
    d.id: {e.id, f.id},
}
vertices = {
    a.id: a,
    b.id: b,
    c.id: c,
    d.id: d,
    e.id: e,
    f.id: f,
}
print(breadth_first_search(a.id, a.id, adjacencies, vertices), 'should be a')
print(breadth_first_search(a.id, e.id, adjacencies, vertices), 'should be e')
print(breadth_first_search(c.id, a.id, adjacencies, vertices), 'should be None')
print(breadth_first_search(a.id, 100, adjacencies, vertices), 'should be None')

Bibliography