Higher-order transformations for inductive graphs — map, filter, fold, and direction changes.
This module provides whole-graph transformations for Yog.Functional.Model.
The functions are intentionally small and explicit: they transform existing
contexts, remove nodes through Model.remove_node/2, or reinterpret/symmetrize
graph direction.
Available Transformations
| Transformation | Function | Description |
|---|---|---|
| Map Nodes | map_nodes/2 | Transform node contexts |
| Map Labels | map_labels/2 | Transform node labels |
| Map Edge Labels | map_edge_labels/2 | Transform edge labels |
| Filter | filter_nodes/2 | Remove nodes whose context does not satisfy a predicate |
| Fold | fold_nodes/3 | Accumulate over all node contexts |
| Reverse | reverse/1 | Flip edge directions in a directed graph |
| To Directed | to_directed/1 | Reinterpret a graph as directed |
| To Undirected | to_undirected/1 | Symmetrize directed edges and mark the graph undirected |
Caveats
map_nodes/2is a low-level context transform. Prefermap_labels/2when changing only labels. If amap_nodes/2callback changes a context'sidor edge maps directly, the caller is responsible for preserving graph invariants.to_directed/1only changes the graph direction flag. It does not remove symmetric edge entries that may already exist in an undirected graph.to_undirected/1symmetrizes every existing directed edge by inserting both directions. If both directions already exist with different labels, the final label is whichever edge is processed last by map iteration order; avoid relying on that case unless labels are identical.
Complexity
Most transformations are O(V + E) where V is the number of nodes and E is
the number of stored edge entries. fold_nodes/3 is O(V).
Summary
Functions
Filters nodes in the graph based on a predicate function.
Folds over all node contexts in the graph.
Transforms the labels of all stored edge entries using the given function.
Transforms the labels of all nodes using the given function, preserving IDs and edges.
Performs a map operation over all node contexts in the graph.
Reverses the direction of all edges in a directed graph.
Reinterprets a graph as directed.
Converts a directed graph to an undirected one by symmetrizing edges.
Functions
@spec filter_nodes(Yog.Functional.Model.t(), (Yog.Functional.Model.Context.t() -> boolean())) :: Yog.Functional.Model.t()
Filters nodes in the graph based on a predicate function.
Contexts for which the predicate returns true are kept. Removed nodes are
deleted via Model.remove_node/2, so incident edge references are also removed
from surviving nodes. The graph direction is preserved.
@spec fold_nodes(Yog.Functional.Model.t(), acc, (Yog.Functional.Model.Context.t(), acc -> acc)) :: acc when acc: any()
Folds over all node contexts in the graph.
The iteration order follows map iteration order and should be treated as unspecified. Use this for order-independent reductions or normalize the result afterwards if order matters.
@spec map_edge_labels(Yog.Functional.Model.t(), (Yog.Functional.Model.edge_label() -> Yog.Functional.Model.edge_label())) :: Yog.Functional.Model.t()
Transforms the labels of all stored edge entries using the given function.
In undirected graphs, edges are represented symmetrically, so both stored directions are transformed. The graph direction is preserved.
Examples
iex> alias Yog.Functional.{Model, Transform}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2, 10)
iex> graph = Transform.map_edge_labels(graph, fn label -> label * 2 end)
iex> Model.get_edge(graph, 1, 2)
{:ok, 20}
@spec map_labels(Yog.Functional.Model.t(), (Yog.Functional.Model.node_label() -> Yog.Functional.Model.node_label())) :: Yog.Functional.Model.t()
Transforms the labels of all nodes using the given function, preserving IDs and edges.
@spec map_nodes(Yog.Functional.Model.t(), (Yog.Functional.Model.Context.t() -> Yog.Functional.Model.Context.t())) :: Yog.Functional.Model.t()
Performs a map operation over all node contexts in the graph.
This is the most general node transform. The returned context is stored under
the original node key, so callbacks should normally preserve ctx.id and should
avoid editing in_edges / out_edges unless they intentionally maintain those
invariants themselves. For label-only updates, prefer map_labels/2.
Examples
iex> alias Yog.Functional.{Model, Transform}
iex> graph = Model.empty() |> Model.put_node(1, "A")
iex> graph = Transform.map_nodes(graph, fn ctx -> %{ctx | label: "B"} end)
iex> {:ok, ctx} = Model.get_node(graph, 1)
iex> ctx.label
"B"
@spec reverse(Yog.Functional.Model.t()) :: Yog.Functional.Model.t()
Reverses the direction of all edges in a directed graph.
For undirected graphs, reversing is an identity operation because both directions are already represented. The graph direction is preserved.
Examples
iex> alias Yog.Functional.{Model, Transform}
iex> graph = Model.empty() |> Model.put_node(1, "A") |> Model.put_node(2, "B")
...> |> Model.add_edge!(1, 2)
iex> graph = Transform.reverse(graph)
iex> Model.has_edge?(graph, 2, 1)
true
iex> Model.has_edge?(graph, 1, 2)
false
@spec to_directed(Yog.Functional.Model.t()) :: Yog.Functional.Model.t()
Reinterprets a graph as directed.
This changes only the direction field. It does not remove any symmetric edge
entries that may exist because the graph used to be undirected.
@spec to_undirected(Yog.Functional.Model.t()) :: Yog.Functional.Model.t()
Converts a directed graph to an undirected one by symmetrizing edges.
Every stored directed edge u -> v becomes an undirected connection represented
internally as both u -> v and v -> u. If opposite directed edges already
exist with different labels, the final undirected label depends on map iteration
order; use identical labels when deterministic conflict resolution matters.