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