yog/transform

Values

pub fn filter_nodes(
  graph: model.Graph(n, e),
  keeping predicate: fn(n) -> Bool,
) -> model.Graph(n, e)

Filters nodes by a predicate, automatically pruning connected edges.

Returns a new graph containing only nodes whose data satisfies the predicate. All edges connected to removed nodes (both incoming and outgoing) are automatically removed to maintain graph consistency.

Time Complexity: O(V + E) where V is nodes and E is edges

Example

let graph =
  model.new(Directed)
  |> model.add_node(1, "apple")
  |> model.add_node(2, "banana")
  |> model.add_node(3, "apricot")
  |> model.add_edge(from: 1, to: 2, with: 1)
  |> model.add_edge(from: 2, to: 3, with: 2)

// Keep only nodes starting with 'a'
let filtered = transform.filter_nodes(graph, fn(s) {
  string.starts_with(s, "a")
})
// Result has nodes 1 and 3, edge 1->2 is removed (node 2 gone)

Use Cases

  • Extract subgraphs based on node properties
  • Remove inactive/disabled nodes from a network
  • Filter by node importance/centrality
pub fn map_edges(
  graph: model.Graph(n, e),
  with fun: fn(e) -> f,
) -> model.Graph(n, f)

Transforms edge weights using a function, preserving graph structure.

This is a functor operation - it applies a function to every edge’s weight/data while keeping all nodes and the graph topology unchanged.

Time Complexity: O(E) where E is the number of edges

Functor Law: map_edges(map_edges(g, f), h) = map_edges(g, fn(x) { h(f(x)) })

Example

let graph =
  model.new(Directed)
  |> model.add_edge(from: 1, to: 2, with: 10)
  |> model.add_edge(from: 2, to: 3, with: 20)

// Double all weights
let doubled = transform.map_edges(graph, fn(w) { w * 2 })
// Edges now have weights 20 and 40

Type Changes

Can change the edge weight type:

// Convert integer weights to floats
transform.map_edges(graph, int.to_float)

// Convert weights to labels
transform.map_edges(graph, fn(w) {
  case w < 10 {
    True -> "short"
    False -> "long"
  }
})
pub fn map_nodes(
  graph: model.Graph(n, e),
  with fun: fn(n) -> m,
) -> model.Graph(m, e)

Transforms node data using a function, preserving graph structure.

This is a functor operation - it applies a function to every node’s data while keeping all edges and the graph structure unchanged.

Time Complexity: O(V) where V is the number of nodes

Functor Law: map_nodes(map_nodes(g, f), h) = map_nodes(g, fn(x) { h(f(x)) })

Example

let graph =
  model.new(Directed)
  |> model.add_node(1, "alice")
  |> model.add_node(2, "bob")

let uppercased = transform.map_nodes(graph, string.uppercase)
// Nodes now contain "ALICE" and "BOB"

Type Changes

Can change the node data type:

// Convert string node data to integers
transform.map_nodes(graph, fn(s) {
  case int.parse(s) {
    Ok(n) -> n
    Error(_) -> 0
  }
})
pub fn merge(
  base: model.Graph(n, e),
  other: model.Graph(n, e),
) -> model.Graph(n, e)

Combines two graphs, with the second graph’s data taking precedence on conflicts.

Merges nodes, out_edges, and in_edges from both graphs. When a node or edge exists in both graphs, the data from the other graph overwrites the base.

The resulting graph uses the kind (Directed/Undirected) from the base graph.

Time Complexity: O(V + E) for both graphs combined

Example

let base =
  model.new(Directed)
  |> model.add_node(1, "Original")
  |> model.add_edge(from: 1, to: 2, with: 10)

let other =
  model.new(Directed)
  |> model.add_node(1, "Updated")
  |> model.add_edge(from: 2, to: 3, with: 20)

let merged = transform.merge(base, other)
// Node 1 has "Updated" (from other)
// Has edges: 1->2 (weight 10) and 2->3 (weight 20)

Use Cases

  • Combining disjoint subgraphs
  • Applying updates/patches to a graph
  • Building graphs incrementally from multiple sources
pub fn transpose(graph: model.Graph(n, e)) -> model.Graph(n, e)

Reverses the direction of every edge in the graph (graph transpose).

Due to the dual-map representation (storing both out_edges and in_edges), this is an O(1) operation - just a pointer swap! This is dramatically faster than most graph libraries where transpose is O(E).

Time Complexity: O(1)

Property: transpose(transpose(G)) = G

Example

let graph =
  model.new(Directed)
  |> model.add_edge(from: 1, to: 2, with: 10)
  |> model.add_edge(from: 2, to: 3, with: 20)

let reversed = transform.transpose(graph)
// Now has edges: 2->1 and 3->2

Use Cases

  • Computing strongly connected components (Kosaraju’s algorithm)
  • Finding all nodes that can reach a target node
  • Reversing dependencies in a DAG
Search Document