Yog.Functional.Transform (YogEx v1.0.0)

Copy Markdown View Source

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

TransformationFunctionDescription
Map Nodesmap_nodes/2Transform node contexts
Map Labelsmap_labels/2Transform node labels
Map Edge Labelsmap_edge_labels/2Transform edge labels
Filterfilter_nodes/2Remove nodes whose context does not satisfy a predicate
Foldfold_nodes/3Accumulate over all node contexts
Reversereverse/1Flip edge directions in a directed graph
To Directedto_directed/1Reinterpret a graph as directed
To Undirectedto_undirected/1Symmetrize directed edges and mark the graph undirected

Caveats

  • map_nodes/2 is a low-level context transform. Prefer map_labels/2 when changing only labels. If a map_nodes/2 callback changes a context's id or edge maps directly, the caller is responsible for preserving graph invariants.
  • to_directed/1 only changes the graph direction flag. It does not remove symmetric edge entries that may already exist in an undirected graph.
  • to_undirected/1 symmetrizes 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

filter_nodes(graph, fun)

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.

fold_nodes(model, initial, fun)

@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.

map_edge_labels(graph, fun)

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}

map_labels(graph, fun)

Transforms the labels of all nodes using the given function, preserving IDs and edges.

map_nodes(graph, fun)

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"

reverse(graph)

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

to_directed(graph)

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.

to_undirected(graph)

@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.