Yog.Functional.Traversal (YogEx v0.98.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 Structure
DFSdfs/2Stack (list)
BFSbfs/2Queue (:queue)
Preorderpreorder/2Node IDs in visit order
Postorderpostorder/2Node IDs in finish order
Reachablereachable/2All reachable node IDs

Key Principle

Iterating with the shrunken graph naturally prevents revisiting nodes and terminates when the graph is empty — no MapSet of visited nodes needed.

Time Complexity: O(V + E) for both BFS and DFS.

References

Summary

Functions

Performs a Breadth-First Search starting from the given node ID(s).

Performs a Depth-First Search starting from the given node ID(s).

Returns node IDs in Postorder (finishing order, last node finishing first).

Returns node IDs in Preorder (visit order).

Returns all node IDs reachable from the start node(s).

Functions

bfs(graph, start_nodes)

Performs a Breadth-First Search starting from the given node ID(s).

Returns a list of node contexts in the order they were visited.

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(s).

Returns a list of node contexts in the order they were visited.

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]

finishing_order(graph, list, acc)

postorder(graph, start_nodes)

Returns node IDs in Postorder (finishing order, last node finishing first).

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 (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(s).

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]