-module(graph). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/graph.gleam"). -export([new/0, nodes/1, size/1, has_node/2, get_context/2, has_edge/3, insert_node/2, insert_directed_edge/4, insert_undirected_edge/4, remove_directed_edge/3, remove_undirected_edge/3, reverse_edges/1, to_directed/1, remove_node/2, match/2, match_any/1]). -export_type([directed/0, undirected/0, graph/3, node_/1, context/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( "\n" " Here's a handy index you can use to browse through the various graph\n" " functions.\n" "\n" " | operation kind | functions |\n" " |---|---|\n" " | creating graphs | [`new`](#new) |\n" " | turning graphs into lists | [`nodes`](#nodes) |\n" " | querying a graph | [`size`](#size), [`has_node`](#has_node), [`has_edge`](#has_edge), [`get_context`](#get_context), [`match`](#match), [`match_any`](#match_any) |\n" " | adding/removing elements from a graph | [`insert_node`](#insert_node), [`insert_directed_edge`](#insert_directed_edge), [`insert_undirected_edge`](#insert_undirected_edge), [`remove_node`](#remove_node), [`remove_directed_edge`](#remove_directed_edge), [`remove_undirected_edge`](#remove_undirected_edge) |\n" " | transforming graphs | [`reverse_edges`](#reverse_edges), [`to_directed`](#to_directed) |\n" "\n" ). -type directed() :: any(). -type undirected() :: any(). -opaque graph(DKT, DKU, DKV) :: {graph, gleam@dict:dict(integer(), context(DKU, DKV))} | {gleam_phantom, DKT}. -type node_(DKW) :: {node, integer(), DKW}. -type context(DKX, DKY) :: {context, gleam@dict:dict(integer(), DKY), node_(DKX), gleam@dict:dict(integer(), DKY)}. -file("src/graph.gleam", 61). ?DOC(" Creates a new empty graph.\n"). -spec new() -> graph(any(), any(), any()). new() -> {graph, maps:new()}. -file("src/graph.gleam", 79). ?DOC( " Returns a list of all the nodes contained in the graph.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " assert [] == graph.new() |> graph.nodes\n" " assert [graph.Node(1, \"\")]\n" " == graph.new()\n" " |> graph.insert_node(graph.Node(1, \"a node\"))\n" " |> graph.nodes\n" " ```\n" ). -spec nodes(graph(any(), DLG, any())) -> list(node_(DLG)). nodes(Graph) -> {graph, Graph@1} = Graph, gleam@dict:fold( Graph@1, [], fun(Acc, _, _use2) -> {context, _, Node, _} = _use2, [Node | Acc] end ). -file("src/graph.gleam", 99). ?DOC( " Returns the number of nodes of the graph.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " assert 0 == graph.new() |> graph.size\n" " assert 1\n" " == graph.new()\n" " |> graph.insert_node(graph.Node(1, \"a node\"))\n" " |> graph.size\n" " ```\n" ). -spec size(graph(any(), any(), any())) -> integer(). size(Graph) -> {graph, Graph@1} = Graph, maps:size(Graph@1). -file("src/graph.gleam", 117). ?DOC( " Returns `True` if the graph contains a node with the given id.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let graph =\n" " graph.new()\n" " |> graph.insert_node(graph.Node(1, \"a node\"))\n" "\n" " assert graph.has_node(graph, 1)\n" " assert !graph.has_node(graph, 2)\n" " ```\n" ). -spec has_node(graph(any(), any(), any()), integer()) -> boolean(). has_node(Graph, Node_id) -> {graph, Graph@1} = Graph, gleam@dict:has_key(Graph@1, Node_id). -file("src/graph.gleam", 162). ?DOC( " Returns the context associated with the node with the given id, if present.\n" " Otherwise returns `Error(Nil)`.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " assert Error(Nil) == graph.new() |> graph.get_context(of: 1)\n" " let assert Ok(graph.Context(node: Node(1, \"a node\"), ..)) =\n" " graph.new()\n" " |> graph.insert_node(graph.Node(1, \"a node\"))\n" " |> graph.get_context(of: 1)\n" " ```\n" ). -spec get_context(graph(any(), DMG, DMH), integer()) -> {ok, context(DMG, DMH)} | {error, nil}. get_context(Graph, Node) -> {graph, Graph@1} = Graph, gleam_stdlib:map_get(Graph@1, Node). -file("src/graph.gleam", 138). ?DOC( " Returns `True` if the graph has an edge connecting the two nodes with the\n" " given ids.\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let graph =\n" " graph.new()\n" " |> graph.insert_node(graph.Node(1, \"a node\"))\n" " |> graph.insert_node(graph.Node(2, \"another node\"))\n" " |> graph.insert_directed_edge(\"label\", from: 1, to: 2)\n" "\n" " assert graph.has_edge(graph, from: 1, to: 2)\n" " assert !graph.has_edge(graph, from: 2, to: 1)\n" " ```\n" ). -spec has_edge(graph(any(), any(), any()), integer(), integer()) -> boolean(). has_edge(Graph, Source, Destination) -> case get_context(Graph, Source) of {ok, {context, _, _, Outgoing}} -> gleam@dict:has_key(Outgoing, Destination); {error, _} -> false end. -file("src/graph.gleam", 210). ?DOC( " Adds a node to the given graph.\n" " If the graph already contains a node with the same id, that will be replaced\n" " by the new one.\n" " The newly added node won't be connected to any existing node.\n" ). -spec insert_node(graph(DNP, DNQ, DNR), node_(DNQ)) -> graph(DNP, DNQ, DNR). insert_node(Graph, Node) -> {graph, Graph@1} = Graph, Empty_context = {context, maps:new(), Node, maps:new()}, New_graph = gleam@dict:insert( Graph@1, erlang:element(2, Node), Empty_context ), {graph, New_graph}. -file("src/graph.gleam", 252). -spec update_context( graph(DOP, DOQ, DOR), integer(), fun((context(DOQ, DOR)) -> context(DOQ, DOR)) ) -> graph(DOP, DOQ, DOR). update_context(Graph, Node, Fun) -> {graph, Graph@1} = Graph, case gleam_stdlib:map_get(Graph@1, Node) of {ok, Context} -> {graph, gleam@dict:insert(Graph@1, Node, Fun(Context))}; {error, _} -> {graph, Graph@1} end. -file("src/graph.gleam", 264). -spec add_outgoing_edge(context(DPC, DPD), integer(), DPD) -> context(DPC, DPD). add_outgoing_edge(Context, Node, Label) -> {context, _, _, Outgoing} = Context, {context, erlang:element(2, Context), erlang:element(3, Context), gleam@dict:insert(Outgoing, Node, Label)}. -file("src/graph.gleam", 273). -spec remove_outgoing_edge(context(DPI, DPJ), integer()) -> context(DPI, DPJ). remove_outgoing_edge(Context, Node) -> {context, _, _, Outgoing} = Context, {context, erlang:element(2, Context), erlang:element(3, Context), gleam@dict:delete(Outgoing, Node)}. -file("src/graph.gleam", 281). -spec add_incoming_edge(context(DPO, DPP), integer(), DPP) -> context(DPO, DPP). add_incoming_edge(Context, Node, Label) -> {context, Incoming, _, _} = Context, {context, gleam@dict:insert(Incoming, Node, Label), erlang:element(3, Context), erlang:element(4, Context)}. -file("src/graph.gleam", 222). ?DOC(" Adds an edge connecting two nodes in a directed graph.\n"). -spec insert_directed_edge( graph(directed(), DNZ, DOA), DOA, integer(), integer() ) -> graph(directed(), DNZ, DOA). insert_directed_edge(Graph, Label, Source, Destination) -> _pipe = Graph, _pipe@1 = update_context( _pipe, Source, fun(_capture) -> add_outgoing_edge(_capture, Destination, Label) end ), update_context( _pipe@1, Destination, fun(_capture@1) -> add_incoming_edge(_capture@1, Source, Label) end ). -file("src/graph.gleam", 235). ?DOC(" Adds an edge connecting two nodes in an undirected graph.\n"). -spec insert_undirected_edge( graph(undirected(), DOH, DOI), DOI, integer(), integer() ) -> graph(undirected(), DOH, DOI). insert_undirected_edge(Graph, Label, One, Other) -> _pipe = Graph, _pipe@2 = update_context( _pipe, One, fun(Context) -> _pipe@1 = add_outgoing_edge(Context, Other, Label), add_incoming_edge(_pipe@1, Other, Label) end ), update_context( _pipe@2, Other, fun(Context@1) -> _pipe@3 = add_outgoing_edge(Context@1, One, Label), add_incoming_edge(_pipe@3, One, Label) end ). -file("src/graph.gleam", 290). -spec remove_incoming_edge(context(DPU, DPV), integer()) -> context(DPU, DPV). remove_incoming_edge(Context, Node) -> {context, Incoming, _, _} = Context, {context, gleam@dict:delete(Incoming, Node), erlang:element(3, Context), erlang:element(4, Context)}. -file("src/graph.gleam", 337). ?DOC(" Removes a directed edge connecting two nodes from a graph.\n"). -spec remove_directed_edge(graph(directed(), DRJ, DRK), integer(), integer()) -> graph(directed(), DRJ, DRK). remove_directed_edge(Graph, Source, Destination) -> _pipe = Graph, _pipe@1 = update_context( _pipe, Source, fun(_capture) -> remove_outgoing_edge(_capture, Destination) end ), update_context( _pipe@1, Destination, fun(_capture@1) -> remove_incoming_edge(_capture@1, Source) end ). -file("src/graph.gleam", 349). ?DOC(" Removes an undirected edge connecting two nodes from a graph.\n"). -spec remove_undirected_edge( graph(undirected(), DRR, DRS), integer(), integer() ) -> graph(undirected(), DRR, DRS). remove_undirected_edge(Graph, One, Other) -> _pipe = Graph, _pipe@2 = update_context( _pipe, One, fun(Context) -> _pipe@1 = remove_outgoing_edge(Context, Other), remove_incoming_edge(_pipe@1, Other) end ), update_context( _pipe@2, Other, fun(Context@1) -> _pipe@3 = remove_outgoing_edge(Context@1, One), remove_incoming_edge(_pipe@3, One) end ). -file("src/graph.gleam", 499). ?DOC( " Flips the direction of every edge in the graph. All incoming edges will\n" " become outgoing and vice-versa.\n" ). -spec reverse_edges(graph(directed(), DRZ, DSA)) -> graph(directed(), DRZ, DSA). reverse_edges(Graph) -> {graph, Graph@1} = Graph, {graph, begin gleam@dict:map_values( Graph@1, fun(_, Context) -> {context, Incoming, Node, Outgoing} = Context, {context, Outgoing, Node, Incoming} end ) end}. -file("src/graph.gleam", 518). ?DOC( " Turns an undirected graph into a directed one. Every edge connecting two\n" " nodes in the original graph will be considered as a pair of edges connecting\n" " the nodes going in both directions.\n" ). -spec to_directed(graph(undirected(), DSH, DSI)) -> graph(directed(), DSH, DSI). to_directed(Graph) -> {graph, Graph@1} = Graph, {graph, Graph@1}. -file("src/graph.gleam", 527). -spec dict_map_shared_keys( gleam@dict:dict(DSP, DSQ), gleam@dict:dict(DSP, DST), fun((DSQ, DST) -> DSQ) ) -> gleam@dict:dict(DSP, DSQ). dict_map_shared_keys(One, Other, Fun) -> gleam@dict:fold( Other, One, fun(One@1, Key, Other_value) -> case gleam_stdlib:map_get(One@1, Key) of {ok, One_value} -> gleam@dict:insert(One@1, Key, Fun(One_value, Other_value)); {error, _} -> One@1 end end ). -file("src/graph.gleam", 315). -spec remove_incoming_occurrences( gleam@dict:dict(integer(), context(DQJ, DQK)), integer(), gleam@dict:dict(integer(), any()) ) -> gleam@dict:dict(integer(), context(DQJ, DQK)). remove_incoming_occurrences(Graph, Node, Nodes) -> dict_map_shared_keys( Graph, Nodes, fun(Context, _) -> {context, Incoming, _, _} = Context, {context, gleam@dict:delete(Incoming, Node), erlang:element(3, Context), erlang:element(4, Context)} end ). -file("src/graph.gleam", 325). -spec remove_outgoing_occurrences( gleam@dict:dict(integer(), context(DQW, DQX)), integer(), gleam@dict:dict(integer(), any()) ) -> gleam@dict:dict(integer(), context(DQW, DQX)). remove_outgoing_occurrences(Graph, Node, Nodes) -> dict_map_shared_keys( Graph, Nodes, fun(Context, _) -> {context, _, _, Outgoing} = Context, {context, erlang:element(2, Context), erlang:element(3, Context), gleam@dict:delete(Outgoing, Node)} end ). -file("src/graph.gleam", 301). ?DOC( " Removes a node with the given id from the graph. If there's no node with the\n" " given id it does nothing.\n" ). -spec remove_node(graph(DQA, DQB, DQC), integer()) -> graph(DQA, DQB, DQC). remove_node(Graph, Node_id) -> case {Graph, get_context(Graph, Node_id)} of {_, {error, _}} -> Graph; {{graph, Graph@1}, {ok, {context, Incoming, _, Outgoing}}} -> _pipe = gleam@dict:delete(Graph@1, Node_id), _pipe@1 = remove_incoming_occurrences(_pipe, Node_id, Outgoing), _pipe@2 = remove_outgoing_occurrences(_pipe@1, Node_id, Incoming), {graph, _pipe@2} end. -file("src/graph.gleam", 175). ?DOC( " If the graph contains a node with the given id, returns a tuple containing\n" " the context of that node (with all edges looping back to itself removed) and\n" " the \"remaining\" graph: that is, the original graph where that node has been\n" " removed.\n" ). -spec match(graph(DMP, DMQ, DMR), integer()) -> {ok, {context(DMQ, DMR), graph(DMP, DMQ, DMR)}} | {error, nil}. match(Graph, Node_id) -> gleam@result:'try'( get_context(Graph, Node_id), fun(_use0) -> {context, Incoming, Node, Outgoing} = _use0, Rest = remove_node(Graph, Node_id), New_incoming = gleam@dict:delete(Incoming, Node_id), New_outgoing = gleam@dict:delete(Outgoing, Node_id), {ok, {{context, New_incoming, Node, New_outgoing}, Rest}} end ). -file("src/graph.gleam", 193). ?DOC( " If the graph contains any node, returns a tuple containing a node of the\n" " graph (with all edges looping back to itself removed) and the \"remaining\"\n" " graph: that is, the original graph where that node has been removed.\n" ). -spec match_any(graph(DNC, DND, DNE)) -> {ok, {context(DND, DNE), graph(DNC, DND, DNE)}} | {error, nil}. match_any(Graph) -> {graph, Dict} = Graph, case maps:keys(Dict) of [] -> {error, nil}; [First | _] -> match(Graph, First) end.