# `Yog.Functional.Transform`
[🔗](https://github.com/code-shoily/yog_ex/blob/v1.0.0/lib/yog/functional/transform.ex#L1)

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/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)`.

# `filter_nodes`

```elixir
@spec filter_nodes(Yog.Functional.Model.t(), (Yog.Functional.Model.Context.t() -&gt;
                                          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.

# `fold_nodes`

```elixir
@spec fold_nodes(Yog.Functional.Model.t(), acc, (Yog.Functional.Model.Context.t(),
                                           acc -&gt;
                                             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`

```elixir
@spec map_edge_labels(Yog.Functional.Model.t(), (Yog.Functional.Model.edge_label() -&gt;
                                             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}

# `map_labels`

```elixir
@spec map_labels(Yog.Functional.Model.t(), (Yog.Functional.Model.node_label() -&gt;
                                        Yog.Functional.Model.node_label())) ::
  Yog.Functional.Model.t()
```

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

# `map_nodes`

```elixir
@spec map_nodes(Yog.Functional.Model.t(), (Yog.Functional.Model.Context.t() -&gt;
                                       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"

# `reverse`

```elixir
@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

# `to_directed`

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

# `to_undirected`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
