Current section

Files

Jump to
yog src yog.gleam
Raw

src/yog.gleam

//// Yog - A comprehensive graph algorithm library for Gleam.
////
//// Provides efficient implementations of classic graph algorithms with a
//// clean, functional API.
////
//// ## Quick Start
////
//// ```gleam
//// 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
//// - **`yog/model`** - Graph data structures and basic operations
//// - Create directed/undirected graphs
//// - Add nodes and edges
//// - Query successors, predecessors, neighbors
////
//// - **`yog/builder/labeled`** - Build graphs with arbitrary labels
//// - Use strings or any type as node identifiers
//// - Automatically maps labels to internal integer IDs
//// - Convert to standard Graph for use with all algorithms
////
//// ### Algorithms
//// - **`yog/pathfinding`** - Shortest path algorithms
//// - Dijkstra's algorithm (non-negative weights)
//// - A* search (with heuristics)
//// - Bellman-Ford (negative weights, cycle detection)
////
//// - **`yog/traversal`** - Graph traversal
//// - Breadth-First Search (BFS)
//// - Depth-First Search (DFS)
//// - Early termination support
////
//// - **`yog/mst`** - Minimum Spanning Tree
//// - Kruskal's algorithm with Union-Find
////
//// - **`yog/topological_sort`** - Topological ordering
//// - Kahn's algorithm
//// - Lexicographical variant (heap-based)
////
//// - **`yog/components`** - Connected components
//// - Tarjan's algorithm for Strongly Connected Components (SCC)
////
//// - **`yog/connectivity`** - Graph connectivity analysis
//// - Tarjan's algorithm for bridges and articulation points
////
//// - **`yog/min_cut`** - Minimum cut algorithms
//// - Stoer-Wagner algorithm for global minimum cut
////
//// ### Data Structures
//// - **`yog/disjoint_set`** - Union-Find / Disjoint Set
//// - Path compression and union by rank
//// - O(α(n)) amortized operations (practically constant)
//// - Dynamic connectivity queries
//// - Generic over any type
////
//// ### Transformations
//// - **`yog/transform`** - Graph transformations
//// - Transpose (O(1) edge reversal!)
//// - Map nodes and edges (functor operations)
//// - Filter nodes with auto-pruning
//// - Merge graphs
////
//// ### Visualization
//// - **`yog/render`** - Graph visualization
//// - Mermaid diagram generation (GitHub/GitLab compatible)
//// - Path highlighting for algorithm results
//// - Customizable node and edge labels
////
//// ## Features
////
//// - **Functional and Immutable**: All operations return new graphs
//// - **Generic**: Works with any node/edge data types
//// - **Type-Safe**: Leverages Gleam's type system
//// - **Well-Tested**: 454+ tests covering all algorithms and data structures
//// - **Efficient**: Optimal data structures (pairing heaps, union-find)
//// - **Documented**: Every function has examples
import yog/model
// Re-export commonly used types for convenience
pub type Graph(node_data, edge_data) =
model.Graph(node_data, edge_data)
pub type NodeId =
model.NodeId
pub type GraphType =
model.GraphType
// Re-export core graph operations for convenience
// This allows users to do: import yog; yog.new(Directed)
// Instead of: import yog/model; model.new(model.Directed)
/// Creates a new empty graph of the specified type.
///
/// ## Example
///
/// ```gleam
/// import yog
/// import yog/model.{Directed}
///
/// let graph = yog.new(Directed)
/// ```
pub fn new(graph_type: GraphType) -> Graph(n, e) {
model.new(graph_type)
}
/// 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
///
/// ```gleam
/// 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 directed() -> Graph(n, e) {
model.new(model.Directed)
}
/// 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
///
/// ```gleam
/// 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)
/// ```
pub fn undirected() -> Graph(n, e) {
model.new(model.Undirected)
}
/// 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
///
/// ```gleam
/// graph
/// |> yog.add_node(1, "Node A")
/// |> yog.add_node(2, "Node B")
/// ```
pub fn add_node(graph: Graph(n, e), id: NodeId, data: n) -> Graph(n, e) {
model.add_node(graph, id, data)
}
/// 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
///
/// ```gleam
/// graph
/// |> yog.add_edge(from: 1, to: 2, with: 10)
/// ```
pub fn add_edge(
graph: Graph(n, e),
from src: NodeId,
to dst: NodeId,
with weight: e,
) -> Graph(n, e) {
model.add_edge(graph, from: src, to: dst, with: weight)
}
/// 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
///
/// ```gleam
/// 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 add_unweighted_edge(
graph: Graph(n, Nil),
from src: NodeId,
to dst: NodeId,
) -> Graph(n, Nil) {
model.add_edge(graph, from: src, to: dst, with: Nil)
}
/// 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
///
/// ```gleam
/// 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_simple_edge(
graph: Graph(n, Int),
from src: NodeId,
to dst: NodeId,
) -> Graph(n, Int) {
model.add_edge(graph, from: src, to: dst, with: 1)
}
/// 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 successors(graph: Graph(n, e), id: NodeId) -> List(#(NodeId, e)) {
model.successors(graph, id)
}
/// 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 predecessors(graph: Graph(n, e), id: NodeId) -> List(#(NodeId, e)) {
model.predecessors(graph, id)
}
/// 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 neighbors(graph: Graph(n, e), id: NodeId) -> List(#(NodeId, e)) {
model.neighbors(graph, id)
}
/// Returns all unique node IDs that have edges in the graph.
pub fn all_nodes(graph: Graph(n, e)) -> List(NodeId) {
model.all_nodes(graph)
}
/// Returns just the NodeIds of successors (without edge data).
/// Convenient for traversal algorithms that only need the IDs.
pub fn successor_ids(graph: Graph(n, e), id: NodeId) -> List(NodeId) {
model.successor_ids(graph, id)
}