defmodule SimpleGraph.Node do @moduledoc """ A Node in the graph. A node has directed edges to others nodes. These nodes are either in the `outgoing` or in the `incoming` list. All adjancy nodes are in the `adjacent` list. """ alias SimpleGraph.Node @type graph_id :: String.t() @type t :: %__MODULE__{ adjacent: [graph_id()], subgraphs: [graph_id()], value: any(), outgoing: [graph_id()], incoming: [graph_id()], id: binary(), parent: graph_id() | nil } @enforce_keys [:value, :id] defstruct adjacent: [], outgoing: [], incoming: [], subgraphs: [], value: "", id: "", parent: nil @spec create_node(any()) :: Node.t() def create_node(value) do %Node{value: value, id: UUID.uuid4(:default)} end @spec add_node(self: Node.t(), outgoing: Node.t(), incoming: Node.t(), subgraph: Node.t()) :: [self: Node.t(), outgoing: Node.t()] | [self: Node.t(), incoming: Node.t()] | [self: Node.t(), subgraph: Node.t()] def add_node(self: %Node{} = self, outgoing: %Node{} = outgoing) do new_outgoing = %Node{ outgoing | adjacent: [self.id | outgoing.adjacent], incoming: [self.id | outgoing.incoming] } [ self: %Node{ self | adjacent: [new_outgoing.id | self.adjacent], outgoing: [new_outgoing.id | self.outgoing] }, outgoing: new_outgoing ] end def add_node(self: %Node{} = self, incoming: %Node{} = incoming) do new_incoming = %Node{ incoming | outgoing: [self.id | incoming.outgoing], adjacent: [self.id | incoming.adjacent] } [ self: %Node{ self | adjacent: [incoming.id | self.adjacent], incoming: [incoming.id | self.incoming] }, incoming: new_incoming ] end def add_node(self: %Node{} = self, subgraph: %Node{} = sub) do new_sub = %Node{sub | parent: self.id} [self: %Node{self | subgraphs: [sub.id | self.subgraphs]}, subgraph: new_sub.id] end @spec remove_node(self: Node.t(), outgoing: Node.t(), incoming: Node.t(), subgraph: Node.t()) :: [self: Node.t(), outgoing: Node.t()] | [self: Node.t(), incoming: Node.t()] | [self: Node.t(), subgraph: Node.t()] def remove_node(self: %Node{} = self, outgoing: %Node{} = outgoing) do new_outgoing = %Node{ outgoing | incoming: outgoing.incoming |> Enum.reject(fn x -> x == self.id end), adjacent: outgoing.adjacent |> Enum.reject(fn x -> x == self.id end) } [ self: %Node{ self | outgoing: self.outgoing |> Enum.filter(&(&1 == outgoing.id)), adjacent: self.adjacent |> Enum.filter(&(&1 == outgoing.id)) }, outgoing: new_outgoing ] end def remove_node(self: %Node{} = self, incoming: %Node{} = incoming) do new_incoming = %Node{ incoming | outgoing: incoming.outgoing |> Enum.reject(&(&1 == self.id)), adjacent: incoming.adjacent |> Enum.reject(&(&1 == self.id)) } [ self: %Node{ self | incoming: self.incoming |> Enum.reject(&(&1 == incoming.id)), adjacent: self.adjacent |> Enum.reject(&(&1 == incoming.id)) }, incoming: new_incoming ] end def remove_node(self: %Node{} = self, subgraph: subgraph) do new_sub = %Node{subgraph | parent: nil} [ self: %Node{self | subgraphs: self.subgraphs |> Enum.reject(&(&1 == subgraph.id))}, subgraph: new_sub ] end end