Core graph data structure and protocols.
A graph is represented as a %Yog.Graph{} struct with four primary fields:
kind: Either:directedor:undirectednodes: Map ofnode_id => node_dataout_edges: Map ofnode_id => %{neighbor_id => weight}in_edges: Map ofnode_id => %{neighbor_id => weight}
Dual-Map Representation
The dual-map design (storing both out_edges and in_edges) enables:
- $\mathcal{O}(1)$ Graph Transpose: Transposing a graph is a simple pointer swap (
out_edges ↔ in_edges). - Fast Predecessor Queries: Finding incoming edges to a node runs in $\mathcal{O}(\text{in-degree})$ time without scanning the entire graph.
- Fast Bidirectional Lookups: Instant checking for reverse edges or symmetrical relationships.
Constructor & Mutation Guidelines
Direct struct instantiation (%Yog.Graph{...}) or raw field mutation should generally
be avoided in application code to prevent index desynchronization between nodes,
out_edges, and in_edges. Use the primary Yog facade or Yog.Model functions to
build and manipulate graphs safely.
Protocols
Yog.Graph implements the Enumerable and Inspect protocols:
- Enumerable: Iterates over nodes as
{id, data}tuples. - Inspect: Compact representation showing graph kind, node count, and edge count.
Visual Showcase
Examples
iex> graph = Yog.Graph.new(:directed)
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 10)
iex> Yog.Graph.node_count(graph)
2
iex> Yog.Graph.edge_count(graph)
1
Summary
Types
Type representing whether a graph is directed or undirected.
Type representing the unique identifier for a node.
Type representing the Yog.Graph structure.
Functions
Returns the total number of edges in the graph.
Creates a new empty graph of the given kind (:directed or :undirected).
Returns the number of nodes in the graph.
Types
@type kind() :: :directed | :undirected
Type representing whether a graph is directed or undirected.
@type node_id() :: term()
Type representing the unique identifier for a node.
@type t() :: %Yog.Graph{ in_edges: %{required(node_id()) => %{required(node_id()) => number()}}, kind: kind(), nodes: %{required(node_id()) => any()}, out_edges: %{required(node_id()) => %{required(node_id()) => number()}} }
Type representing the Yog.Graph structure.
Functions
@spec edge_count(t()) :: non_neg_integer()
Returns the total number of edges in the graph.
For undirected graphs, each edge is counted once (excluding duplicate mirroring). Self-loops are counted as single edges.
Time Complexity: $\mathcal{O}(V)$
Errors
- Raises
ArgumentErrorif passed a non-Yog.Graphterm.
Examples
iex> graph = Yog.Graph.new(:directed)
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 10)
...> |> Yog.add_edge_ensure(from: 1, to: 3, with: 20)
iex> Yog.Graph.edge_count(graph)
2
Creates a new empty graph of the given kind (:directed or :undirected).
Errors
- Raises
ArgumentErrorifkindis not:directedor:undirected.
Examples
iex> Yog.Graph.new(:directed)
%Yog.Graph{kind: :directed, in_edges: %{}, nodes: %{}, out_edges: %{}}
iex> Yog.Graph.new(:undirected)
%Yog.Graph{kind: :undirected, in_edges: %{}, nodes: %{}, out_edges: %{}}
@spec node_count(t()) :: non_neg_integer()
Returns the number of nodes in the graph.
Time Complexity: $\mathcal{O}(1)$
Errors
- Raises
ArgumentErrorif passed a non-Yog.Graphterm.
Examples
iex> graph = Yog.Graph.new(:directed)
...> |> Yog.add_node(1, "A")
...> |> Yog.add_node(2, "B")
...> |> Yog.add_edge_ensure(from: 1, to: 2, with: 10)
iex> Yog.Graph.node_count(graph)
2