Yog.Functional.Traversal (YogEx v1.0.0)

Copy Markdown View Source

Inductive graph traversals — BFS and DFS without explicit visited sets.

Unlike traditional graph traversals that maintain a separate "visited" set, these implementations rely on the structural match/2 operation. When a node is extracted, it is removed from the graph along with all its incident edges, so the shrunken graph naturally prevents revisits.

Available Traversals

TraversalFunctionData StructureReturn value
DFSdfs/2Stack (list)Node contexts
BFSbfs/2Queue (:queue)Node contexts
Preorderpreorder/2DFS wrapperNode IDs in visit order
Postorderpostorder/2Recursive finishing orderNode IDs in finish order
Reachablereachable/2DFS wrapperReachable node IDs

Key Principle

Iterating with the shrunken graph naturally prevents revisiting nodes and terminates when no queued start/candidate nodes remain — no MapSet of visited nodes is needed.

Semantics

  • Traversal starts can be a single node ID or a list of node IDs.
  • Missing start nodes are skipped.
  • Duplicate start nodes or duplicate neighbor discoveries are visited at most once because already-matched nodes are absent from the remaining graph.
  • Directed graphs follow outgoing edges only. Undirected graphs store edges symmetrically, so outgoing edges represent all adjacent nodes.
  • Neighbor order follows map key iteration order and should be treated as unspecified.

Complexity

BFS and DFS are O(V + E) over the reachable portion of the graph. Because this is an inductive/persistent representation, traversals allocate shrunken graph versions as they progress; use adjacency-based Yog.Traversal.Walk for raw traversal throughput on large production graphs.

References

Summary

Functions

Performs a breadth-first search starting from the given node ID or node IDs.

Performs a depth-first search starting from the given node ID or node IDs.

Returns node IDs in postorder (finishing order).

Returns node IDs in preorder (DFS visit order).

Returns all node IDs reachable from the start node or node IDs.

Functions

bfs(graph, start_nodes)

Performs a breadth-first search starting from the given node ID or node IDs.

Returns a list of node contexts in the order they were visited. Missing start nodes are skipped.

This is an inductive BFS: each step calls match/2 to extract a node and obtain the shrunken graph, ensuring nodes are visited at most once.

Examples

iex> alias Yog.Functional.{Model, Traversal}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> visited = Traversal.bfs(graph, 1)
iex> Enum.map(visited, & &1.id)
[1, 2]

dfs(graph, start_nodes)

Performs a depth-first search starting from the given node ID or node IDs.

Returns a list of node contexts in the order they were visited. Missing start nodes are skipped.

This is an inductive DFS: each step calls match/2 to simultaneously extract a node and obtain the shrunken graph without that node. Revisit prevention comes naturally from the graph shrinking — a node already visited simply won't be found in the remaining graph.

For directed graphs, only outgoing edges are followed. For undirected graphs, edges are stored symmetrically so out_edges covers all adjacent nodes.

Examples

iex> alias Yog.Functional.{Model, Traversal}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> visited = Traversal.dfs(graph, 1)
iex> Enum.map(visited, & &1.id)
[1, 2]

postorder(graph, start_nodes)

Returns node IDs in postorder (finishing order).

Examples

iex> alias Yog.Functional.{Model, Traversal}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> Traversal.postorder(graph, 1)
[2, 1]

preorder(graph, start)

Returns node IDs in preorder (DFS visit order).

Examples

iex> alias Yog.Functional.{Model, Traversal}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> Traversal.preorder(graph, 1)
[1, 2]

reachable(graph, start)

Returns all node IDs reachable from the start node or node IDs.

This is a thin wrapper around dfs/2, returning IDs instead of contexts.

Examples

iex> alias Yog.Functional.{Model, Traversal}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> Traversal.reachable(graph, 1) |> Enum.sort()
[1, 2]