yog

Yog - A comprehensive graph algorithm library for Gleam.

Provides efficient implementations of classic graph algorithms with a clean, functional API.

Quick Start

import yog
import yog/pathfinding
import gleam/int

pub fn main() {
  let graph =
    yog.directed()
    |> yog.add_node(1, "Start")
    |> yog.add_node(2, "Middle")
    |> yog.add_node(3, "End")
    |> yog.add_edge(from: 1, to: 2, with: 5)
    |> yog.add_edge(from: 2, to: 3, with: 3)
    |> yog.add_edge(from: 1, to: 3, with: 10)

  case pathfinding.shortest_path(
    in: graph,
    from: 1,
    to: 3,
    with_zero: 0,
    with_add: int.add,
    with_compare: int.compare
  ) {
    Some(path) -> {
      // Path(nodes: [1, 2, 3], total_weight: 8)
      io.println("Shortest path found!")
    }
    None -> io.println("No path exists")
  }
}

Modules

Core

Algorithms

Data Structures

Transformations

Visualization

Features

Types

pub type Graph(node_data, edge_data) =
  model.Graph(node_data, edge_data)
pub type GraphType =
  model.GraphType
pub type NodeId =
  Int

Values

pub fn add_edge(
  graph: model.Graph(n, e),
  from src: Int,
  to dst: Int,
  with weight: e,
) -> model.Graph(n, e)

Adds an edge to the graph with the given weight.

For directed graphs, adds a single edge from src to dst. For undirected graphs, adds edges in both directions.

Example

graph
|> yog.add_edge(from: 1, to: 2, with: 10)
pub fn add_edge_ensured(
  graph: model.Graph(n, e),
  from src: Int,
  to dst: Int,
  with weight: e,
  default default: n,
) -> model.Graph(n, e)

Like add_edge, but ensures both endpoint nodes exist first.

If src or dst is not already in the graph, it is created with the supplied default node data. Existing nodes are left unchanged.

Example

yog.directed()
|> yog.add_edge_ensured(from: 1, to: 2, with: 10, default: "anon")
// Nodes 1 and 2 are auto-created with data "anon"
pub fn add_node(
  graph: model.Graph(n, e),
  id: Int,
  data: n,
) -> model.Graph(n, e)

Adds a node to the graph with the given ID and data. If a node with this ID already exists, its data will be replaced.

Example

graph
|> yog.add_node(1, "Node A")
|> yog.add_node(2, "Node B")
pub fn add_simple_edge(
  graph: model.Graph(n, Int),
  from src: Int,
  to dst: Int,
) -> model.Graph(n, Int)

Adds a simple edge with weight 1.

This is a convenience function for graphs with integer weights where a default weight of 1 is appropriate (e.g., unweighted graphs, hop counts).

Example

graph
|> yog.add_simple_edge(from: 1, to: 2)
|> yog.add_simple_edge(from: 2, to: 3)
// Both edges have weight 1
pub fn add_unweighted_edge(
  graph: model.Graph(n, Nil),
  from src: Int,
  to dst: Int,
) -> model.Graph(n, Nil)

Adds an unweighted edge to the graph.

This is a convenience function for graphs where edges have no meaningful weight. Uses Nil as the edge data type.

Example

let graph: Graph(String, Nil) = yog.directed()
  |> yog.add_node(1, "A")
  |> yog.add_node(2, "B")
  |> yog.add_unweighted_edge(from: 1, to: 2)
pub fn all_nodes(graph: model.Graph(n, e)) -> List(Int)

Returns all unique node IDs that have edges in the graph.

pub fn directed() -> model.Graph(n, e)

Creates a new empty directed graph.

This is a convenience function that’s equivalent to yog.new(Directed), but requires only a single import.

Example

import yog

let graph =
  yog.directed()
  |> yog.add_node(1, "Start")
  |> yog.add_node(2, "End")
  |> yog.add_edge(from: 1, to: 2, with: 10)
pub fn from_adjacency_list(
  graph_type: model.GraphType,
  adj_list: List(#(Int, List(#(Int, e)))),
) -> model.Graph(Nil, e)

Creates a graph from an adjacency list #(src, List(#(dst, weight))).

Example

let graph = yog.from_adjacency_list(model.Directed, [#(1, [#(2, 10), #(3, 5)])])
pub fn from_edges(
  graph_type: model.GraphType,
  edges: List(#(Int, Int, e)),
) -> model.Graph(Nil, e)

Creates a graph from a list of edges #(src, dst, weight).

Example

let graph = yog.from_edges(model.Directed, [#(1, 2, 10), #(2, 3, 5)])
pub fn from_unweighted_edges(
  graph_type: model.GraphType,
  edges: List(#(Int, Int)),
) -> model.Graph(Nil, Nil)

Creates a graph from a list of unweighted edges #(src, dst).

Example

let graph = yog.from_unweighted_edges(model.Directed, [#(1, 2), #(2, 3)])
pub fn is_acyclic(graph: model.Graph(n, e)) -> Bool

Determines if a graph is acyclic (contains no cycles).

This is the logical opposite of is_cyclic. For directed graphs, returning True means the graph is a Directed Acyclic Graph (DAG).

Time Complexity: O(V + E)

Example

yog.is_acyclic(graph)
// => True // Valid DAG or undirected forest
pub fn is_cyclic(graph: model.Graph(n, e)) -> Bool

Determines if a graph contains any cycles.

For directed graphs, a cycle exists if there is a path from a node back to itself. For undirected graphs, a cycle exists if there is a path of length >= 3 from a node back to itself, or a self-loop.

Time Complexity: O(V + E)

Example

yog.is_cyclic(graph)
// => True // Cycle detected
pub fn neighbors(
  graph: model.Graph(n, e),
  id: Int,
) -> List(#(Int, e))

Gets all nodes connected to the given node, regardless of direction. For undirected graphs, this is equivalent to successors. For directed graphs, this combines successors and predecessors.

pub fn new(graph_type: model.GraphType) -> model.Graph(n, e)

Creates a new empty graph of the specified type.

Example

import yog
import yog/model.{Directed}

let graph = yog.new(Directed)
pub fn predecessors(
  graph: model.Graph(n, e),
  id: Int,
) -> List(#(Int, e))

Gets nodes you came FROM to reach the given node (predecessors). Returns a list of tuples containing the source node ID and edge data.

pub fn successor_ids(
  graph: model.Graph(n, e),
  id: Int,
) -> List(Int)

Returns just the NodeIds of successors (without edge data). Convenient for traversal algorithms that only need the IDs.

pub fn successors(
  graph: model.Graph(n, e),
  id: Int,
) -> List(#(Int, e))

Gets nodes you can travel TO from the given node (successors). Returns a list of tuples containing the destination node ID and edge data.

pub fn undirected() -> model.Graph(n, e)

Creates a new empty undirected graph.

This is a convenience function that’s equivalent to yog.new(Undirected), but requires only a single import.

Example

import yog

let graph =
  yog.undirected()
  |> yog.add_node(1, "A")
  |> yog.add_node(2, "B")
  |> yog.add_edge(from: 1, to: 2, with: 5)
Search Document