Graph walking algorithms — BFS, DFS, Best-First, and Random traversals.
This module provides a unified API for exploring graphs starting from a given node. It supports both standard discovery (BFS, DFS) and informed discovery where the next nodes are prioritized based on weights, heuristics, or randomness.
Discovery Visualization
Traversals like BFS explore a graph in layers, prioritizing nodes based on their distance from the source.
iex> alias Yog.Traversal.Walk
iex> graph = Yog.from_edges(:directed, [
...> {"Root", "A", 1}, {"Root", "B", 1},
...> {"A", "C", 1}, {"A", "D", 1}, {"B", "E", 1}
...> ])
iex> Walk.walk(in: graph, from: "Root", using: :breadth_first)
["Root", "A", "B", "C", "D", "E"]
Summary
Functions
Finds the shortest path between two nodes using BFS.
Folds over nodes during graph traversal, accumulating state with metadata.
Performs a random walk of fixed length from the given starting node.
Checks if there is a path between two nodes.
Walks the graph starting from the given node, visiting all reachable nodes.
Walks the graph but stops early when a condition is met.
Types
@type order() :: :breadth_first | :depth_first | :best_first | :random
@type priority_fn() :: (Yog.node_id(), number(), walk_metadata() -> number())
@type walk_control() :: :continue | :stop | :halt
@type walk_metadata() :: %{depth: integer(), parent: Yog.node_id() | nil}
Functions
@spec find_path(Yog.graph(), Yog.node_id(), Yog.node_id()) :: [Yog.node_id()] | nil
Finds the shortest path between two nodes using BFS.
Folds over nodes during graph traversal, accumulating state with metadata.
The folder function receives (accumulator, node_id, metadata) and should
return {control, new_accumulator}.
Metadata
The metadata map contains:
:depth- Distance from the starting node.:parent- The node ID that led to this node.
Control Signals
:continue- Continue traversal normally.:stop- Do not explore successors of the current node, but continue with the rest of the frontier.:halt- Stop the entire traversal immediately.
@spec fold_walk( Yog.graph(), Yog.node_id(), order(), acc, (acc, Yog.node_id(), walk_metadata() -> {walk_control(), acc}) ) :: acc when acc: var
@spec random_walk(Yog.graph(), Yog.node_id(), integer()) :: [Yog.node_id()]
Performs a random walk of fixed length from the given starting node.
Unlike discovery traversals (BFS/DFS), a random walk does not keep track of visited nodes and may cross the same node or edge multiple times.
Parameters
graph: The graph to walk.from: The starting node ID.steps: The number of jumps/edges to transition (path length in edges).
Examples
iex> graph = Yog.directed() |> Yog.add_edge_ensure(1, 2, 1, nil) |> Yog.add_edge_ensure(2, 1, 1, nil)
iex> path = Yog.Traversal.Walk.random_walk(graph, 1, 3)
iex> length(path)
4
@spec reachable?(Yog.graph(), Yog.node_id(), Yog.node_id()) :: boolean()
Checks if there is a path between two nodes.
@spec walk(keyword()) :: [Yog.node_id()]
Walks the graph starting from the given node, visiting all reachable nodes.
Options
:from- Starting node ID:in- The graph to traverse:using- Traversal order. Options::breadth_first(BFS):depth_first(DFS):best_first- Prioritizes discovery based on a:priorityfunction.:random- Randomizes discovery order.
:priority- Required if:usingis:best_first. A function taking(node_id, weight, meta).
Examples
# Simple BFS walk
iex> graph = Yog.directed() |> Yog.add_edge_ensure(1, 2, 1, nil)
iex> Yog.Traversal.Walk.walk(in: graph, from: 1, using: :breadth_first)
[1, 2]
# Greedy walk using edge weights (lowest weight first)
iex> graph = Yog.directed() |> Yog.add_edge_ensure(1, 2, 10, nil) |> Yog.add_edge_ensure(1, 3, 5, nil)
iex> Yog.Traversal.Walk.walk(in: graph, from: 1, using: :best_first, priority: fn _, w, _ -> w end)
[1, 3, 2]
@spec walk(Yog.graph(), Yog.node_id(), order()) :: [Yog.node_id()]
@spec walk_until(keyword()) :: [Yog.node_id()]
Walks the graph but stops early when a condition is met.
@spec walk_until(Yog.graph(), Yog.node_id(), order(), (Yog.node_id() -> boolean())) :: [Yog.node_id()]