-module(yog@builder@labeled). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/yog/builder/labeled.gleam"). -export([new/1, directed/0, undirected/0, ensure_node/2, add_node/2, add_edge/4, add_unweighted_edge/3, add_simple_edge/3, get_id/2, to_graph/1, from_list/2, from_unweighted_list/2, all_labels/1, successors/2, predecessors/2]). -export_type([builder/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " A builder for creating graphs using arbitrary labels instead of integer IDs.\n" "\n" " This module provides a convenient way to build graphs when your nodes are\n" " naturally identified by strings or other types, rather than integers. The\n" " builder maintains a mapping from labels to internal integer IDs and\n" " converts to a standard `Graph` when needed.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" " import yog/pathfinding\n" " import gleam/int\n" "\n" " pub fn main() {\n" " // Build a graph using string labels\n" " let builder =\n" " labeled.directed()\n" " |> labeled.add_edge(\"home\", \"work\", 10)\n" " |> labeled.add_edge(\"work\", \"gym\", 5)\n" " |> labeled.add_edge(\"home\", \"gym\", 12)\n" "\n" " // Convert to a Graph to use with algorithms\n" " let graph = labeled.to_graph(builder)\n" "\n" " // Get the node IDs for the labels we care about\n" " let assert Ok(home_id) = labeled.get_id(builder, \"home\")\n" " let assert Ok(gym_id) = labeled.get_id(builder, \"gym\")\n" "\n" " // Now use standard graph algorithms\n" " case pathfinding.shortest_path(\n" " in: graph,\n" " from: home_id,\n" " to: gym_id,\n" " with_zero: 0,\n" " with_add: int.add,\n" " with_compare: int.compare,\n" " ) {\n" " Ok(path) -> // Path found!\n" " Error(_) -> // No path\n" " }\n" " }\n" " ```\n" ). -type builder(KRH, KRI) :: {builder, yog@model:graph(KRH, KRI), gleam@dict:dict(KRH, integer()), integer()}. -file("src/yog/builder/labeled.gleam", 75). ?DOC( " Creates a new empty labeled graph builder.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" " import yog/model.{Directed}\n" "\n" " let builder = labeled.new(Directed)\n" " ```\n" ). -spec new(yog@model:graph_type()) -> builder(any(), any()). new(Graph_type) -> {builder, yog@model:new(Graph_type), maps:new(), 0}. -file("src/yog/builder/labeled.gleam", 92). ?DOC( " Creates a new empty labeled directed graph builder.\n" "\n" " This is a convenience function equivalent to `labeled.new(Directed)`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" "\n" " let builder =\n" " labeled.directed()\n" " |> labeled.add_edge(\"home\", \"work\", 10)\n" " ```\n" ). -spec directed() -> builder(any(), any()). directed() -> {builder, yog@model:new(directed), maps:new(), 0}. -file("src/yog/builder/labeled.gleam", 109). ?DOC( " Creates a new empty labeled undirected graph builder.\n" "\n" " This is a convenience function equivalent to `labeled.new(Undirected)`.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" "\n" " let builder =\n" " labeled.undirected()\n" " |> labeled.add_edge(\"A\", \"B\", 5)\n" " ```\n" ). -spec undirected() -> builder(any(), any()). undirected() -> {builder, yog@model:new(undirected), maps:new(), 0}. -file("src/yog/builder/labeled.gleam", 129). ?DOC( " Gets or creates a node for the given label, returning the builder and node ID.\n" "\n" " If a node with this label already exists, returns its ID without modification.\n" " If it doesn't exist, creates a new node with the label as its data.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let #(builder, node_a) = labeled.ensure_node(builder, \"Node A\")\n" " let #(builder, node_b) = labeled.ensure_node(builder, \"Node B\")\n" " // Now you have the IDs and can use them with lower-level operations\n" " ```\n" ). -spec ensure_node(builder(KRV, KRW), KRV) -> {builder(KRV, KRW), integer()}. ensure_node(Builder, Label) -> case gleam_stdlib:map_get(erlang:element(3, Builder), Label) of {ok, Id} -> {Builder, Id}; {error, _} -> Id@1 = erlang:element(4, Builder), New_graph = yog@model:add_node( erlang:element(2, Builder), Id@1, Label ), New_mapping = gleam@dict:insert( erlang:element(3, Builder), Label, Id@1 ), {{builder, New_graph, New_mapping, Id@1 + 1}, Id@1} end. -file("src/yog/builder/labeled.gleam", 160). ?DOC( " Adds a node with the given label explicitly.\n" "\n" " If a node with this label already exists, its data will be replaced.\n" " This is useful when you want to add nodes before adding edges.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " builder\n" " |> labeled.add_node(\"Node A\")\n" " |> labeled.add_node(\"Node B\")\n" " |> labeled.add_edge(\"Node A\", \"Node B\", 5)\n" " ```\n" ). -spec add_node(builder(KSB, KSC), KSB) -> builder(KSB, KSC). add_node(Builder, Label) -> {New_builder, _} = ensure_node(Builder, Label), New_builder. -file("src/yog/builder/labeled.gleam", 178). ?DOC( " Adds an edge between two labeled nodes.\n" "\n" " If either node doesn't exist, it will be created automatically.\n" " For directed graphs, adds a single edge from `from` to `to`.\n" " For undirected graphs, adds edges in both directions.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " builder\n" " |> labeled.add_edge(from: \"A\", to: \"B\", with: 10)\n" " |> labeled.add_edge(from: \"B\", to: \"C\", with: 5)\n" " ```\n" ). -spec add_edge(builder(KSH, KSI), KSH, KSH, KSI) -> builder(KSH, KSI). add_edge(Builder, Src_label, Dst_label, Weight) -> {Builder@1, Src_id} = ensure_node(Builder, Src_label), {Builder@2, Dst_id} = ensure_node(Builder@1, Dst_label), New_graph = yog@model:add_edge( erlang:element(2, Builder@2), Src_id, Dst_id, Weight ), {builder, New_graph, erlang:element(3, Builder@2), erlang:element(4, Builder@2)}. -file("src/yog/builder/labeled.gleam", 207). ?DOC( " Adds an unweighted edge between two labeled nodes.\n" "\n" " This is a convenience function for graphs where edges have no meaningful weight.\n" " Uses `Nil` as the edge data type. Nodes are created automatically if they don't exist.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" "\n" " let builder: labeled.Builder(String, Nil) = labeled.directed()\n" " |> labeled.add_unweighted_edge(\"A\", \"B\")\n" " |> labeled.add_unweighted_edge(\"B\", \"C\")\n" " ```\n" ). -spec add_unweighted_edge(builder(KSN, nil), KSN, KSN) -> builder(KSN, nil). add_unweighted_edge(Builder, Src_label, Dst_label) -> {Builder@1, Src_id} = ensure_node(Builder, Src_label), {Builder@2, Dst_id} = ensure_node(Builder@1, Dst_label), New_graph = yog@model:add_edge( erlang:element(2, Builder@2), Src_id, Dst_id, nil ), {builder, New_graph, erlang:element(3, Builder@2), erlang:element(4, Builder@2)}. -file("src/yog/builder/labeled.gleam", 237). ?DOC( " Adds a simple edge with weight 1 between two labeled nodes.\n" "\n" " This is a convenience function for graphs with integer weights where\n" " a default weight of 1 is appropriate (e.g., unweighted graphs, hop counts).\n" " Nodes are created automatically if they don't exist.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import yog/builder/labeled\n" "\n" " let builder = labeled.directed()\n" " |> labeled.add_simple_edge(\"home\", \"work\")\n" " |> labeled.add_simple_edge(\"work\", \"gym\")\n" " // Both edges have weight 1\n" " ```\n" ). -spec add_simple_edge(builder(KSS, integer()), KSS, KSS) -> builder(KSS, integer()). add_simple_edge(Builder, Src_label, Dst_label) -> {Builder@1, Src_id} = ensure_node(Builder, Src_label), {Builder@2, Dst_id} = ensure_node(Builder@1, Dst_label), New_graph = yog@model:add_edge( erlang:element(2, Builder@2), Src_id, Dst_id, 1 ), {builder, New_graph, erlang:element(3, Builder@2), erlang:element(4, Builder@2)}. -file("src/yog/builder/labeled.gleam", 263). ?DOC( " Looks up the internal node ID for a given label.\n" "\n" " Returns `Ok(id)` if the label exists, `Error(Nil)` if it doesn't.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case labeled.get_id(builder, \"Node A\") {\n" " Ok(id) -> // Use the ID\n" " Error(_) -> // Label doesn't exist\n" " }\n" " ```\n" ). -spec get_id(builder(KSX, any()), KSX) -> {ok, integer()} | {error, nil}. get_id(Builder, Label) -> gleam_stdlib:map_get(erlang:element(3, Builder), Label). -file("src/yog/builder/labeled.gleam", 278). ?DOC( " Converts the builder to a standard `Graph`.\n" "\n" " The resulting graph uses integer IDs internally and stores the labels\n" " as node data. This graph can be used with all yog algorithms.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let graph = labeled.to_graph(builder)\n" " // Now use with pathfinding, traversal, etc.\n" " ```\n" ). -spec to_graph(builder(KTD, KTE)) -> yog@model:graph(KTD, KTE). to_graph(Builder) -> erlang:element(2, Builder). -file("src/yog/builder/labeled.gleam", 289). ?DOC( " Creates a labeled graph builder from a list of edges #(src_label, dst_label, weight).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let builder = labeled.from_list(model.Directed, [#(\"A\", \"B\", 10), #(\"B\", \"C\", 5)])\n" " ```\n" ). -spec from_list(yog@model:graph_type(), list({KTJ, KTJ, KTK})) -> builder(KTJ, KTK). from_list(Graph_type, Edges) -> gleam@list:fold( Edges, new(Graph_type), fun(B, Edge) -> {Src, Dst, Weight} = Edge, add_edge(B, Src, Dst, Weight) end ). -file("src/yog/builder/labeled.gleam", 306). ?DOC( " Creates a labeled graph builder from a list of unweighted edges #(src_label, dst_label).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let builder = labeled.from_unweighted_list(model.Directed, [#(\"A\", \"B\"), #(\"B\", \"C\")])\n" " ```\n" ). -spec from_unweighted_list(yog@model:graph_type(), list({KTO, KTO})) -> builder(KTO, nil). from_unweighted_list(Graph_type, Edges) -> gleam@list:fold( Edges, new(Graph_type), fun(B, Edge) -> {Src, Dst} = Edge, add_unweighted_edge(B, Src, Dst) end ). -file("src/yog/builder/labeled.gleam", 324). ?DOC( " Returns all labels that have been added to the builder.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let labels = labeled.all_labels(builder)\n" " // [\"Node A\", \"Node B\", \"Node C\"]\n" " ```\n" ). -spec all_labels(builder(KTS, any())) -> list(KTS). all_labels(Builder) -> maps:keys(erlang:element(3, Builder)). -file("src/yog/builder/labeled.gleam", 379). -spec list_map_ids_to_labels(list({integer(), KUL}), yog@model:graph(KUN, KUL)) -> list({KUN, KUL}). list_map_ids_to_labels(Edges, Graph) -> _pipe = Edges, gleam@list:filter_map( _pipe, fun(Edge) -> {Node_id, Edge_data} = Edge, case gleam_stdlib:map_get(erlang:element(3, Graph), Node_id) of {ok, Label} -> {ok, {Label, Edge_data}}; {error, _} -> {error, nil} end end ). -file("src/yog/builder/labeled.gleam", 340). ?DOC( " Gets the successors of a node by its label.\n" "\n" " Returns a list of tuples containing the successor's label and edge data.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case labeled.successors(builder, \"Node A\") {\n" " Ok(successors) -> // List of #(label, edge_data)\n" " Error(_) -> // Node doesn't exist\n" " }\n" " ```\n" ). -spec successors(builder(KTX, KTY), KTX) -> {ok, list({KTX, KTY})} | {error, nil}. successors(Builder, Label) -> gleam@result:'try'( get_id(Builder, Label), fun(Id) -> Successor_edges = yog@model:successors( erlang:element(2, Builder), Id ), _pipe = Successor_edges, _pipe@1 = list_map_ids_to_labels(_pipe, erlang:element(2, Builder)), {ok, _pipe@1} end ). -file("src/yog/builder/labeled.gleam", 365). ?DOC( " Gets the predecessors of a node by its label.\n" "\n" " Returns a list of tuples containing the predecessor's label and edge data.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " case labeled.predecessors(builder, \"Node A\") {\n" " Ok(predecessors) -> // List of #(label, edge_data)\n" " Error(_) -> // Node doesn't exist\n" " }\n" " ```\n" ). -spec predecessors(builder(KUE, KUF), KUE) -> {ok, list({KUE, KUF})} | {error, nil}. predecessors(Builder, Label) -> gleam@result:'try'( get_id(Builder, Label), fun(Id) -> Predecessor_edges = yog@model:predecessors( erlang:element(2, Builder), Id ), _pipe = Predecessor_edges, _pipe@1 = list_map_ids_to_labels(_pipe, erlang:element(2, Builder)), {ok, _pipe@1} end ).