lib/tree_map.ex

defmodule TreeMap do
  @moduledoc """
  A module to maintain a tree of the structure

  ```elixir
  %Node{ key: ..., value: ..., children: [%TreeMap{},...]}
  ```

  and functions to traverse such a tree.

  ### Examples

  ```elixir
  root =
   TreeMap.new("1", "Root")
   |> TreeMap.add_child("1.1", "Sub One", [TreeMap.new("1.1.1", "SomeSubSub")])
   |> TreeMap.add_child("1.2", "Sub Two", [TreeMap.new("1.2.1", "Some other SubSub")])

  expected = ["1", "1.1", "1.1.1", "1.2", "1.2.1"]

  received =
   TreeMap.transduce(root, [], fn a, x -> a ++ [x] end, fn n, acc -> [n | acc] end)
   |> Enum.map(& &1.key)

  assert expected == received
  ```
  """

  alias TreeMap.{Node, RootServer}

  @doc """
  ### Examples

      iex> TreeMap.new()
      %TreeMap.Node{}

      iex> TreeMap.new(123, "hundretandtwentythree")
      %TreeMap.Node{ key: 123, value: "hundretandtwentythree"}

  """
  def new(key \\ nil, value \\ nil, children \\ []) do
    %Node{key: key, value: value, children: children}
  end

  @doc """
  Return the children of this node.
  """
  def children(tree_map), do: tree_map.children

  @doc """
  Prepend a new child to the children of this node.

  ### Example:
      iex> TreeMap.new("1", "Root") |> TreeMap.add_child("1.1", "Sub One")
      %TreeMap.Node{key: "1", value: "Root", children: [%TreeMap.Node{key: "1.1", value: "Sub One"}]}
  """
  def add_child(tree_map, key, value, children \\ []) do
    %{tree_map | children: [new(key, value, children) | tree_map.children]}
  end

  @doc """
  Prepend an exisitng child to the children of this node.

  ### Example:
      iex> child = TreeMap.new("0", "I exist")
      iex> TreeMap.new("1", "Root") |> TreeMap.add_child(child)
      %TreeMap.Node{key: "1", value: "Root", children: [%TreeMap.Node{key: "0", value: "I exist"}]}
  """
  def add_child(tree_map, child) do
    %{tree_map | children: [child | tree_map.children]}
  end

  @doc """
  Traverse the tree map and apply the given function to each node.

  ### Example: 
      iex> root = TreeMap.new("1", "Root") |> TreeMap.add_child("1.1", "Sub One") |> TreeMap.add_child("1.2", "Sub Two")
      ...> TreeMap.traverse(root, fn x -> x.key end)
      ["1", ["1.1", [], "1.2", []]]
        
  """
  def traverse(tree_map, fun) do
    [
      fun.(tree_map)
      | [
          Enum.flat_map(Enum.reverse(tree_map.children), fn child ->
            traverse(child, fun)
          end)
        ]
    ]
  end

  @doc """
  Traverse the tree map and apply the given function to each node 
    and a reducer-function to it's children.

  ### Example: 
      iex> root = TreeMap.new("1", "Root") 
      ...>        |> TreeMap.add_child("1.1", "Sub One", [TreeMap.new("1.1.1", "SomeSubSub")]) 
      ...>        |> TreeMap.add_child("1.2", "Sub Two", [TreeMap.new("1.2.1", "Some other SubSub")])
      ...> TreeMap.transduce(root, 38, fn a, x -> a + Enum.count(x.children) end, fn x, acc -> acc + x end)
      42

      iex> root = TreeMap.new("1", "Root") 
      ...>        |> TreeMap.add_child("1.1", "Sub One", [TreeMap.new("1.1.1", "SomeSubSub")]) 
      ...>        |> TreeMap.add_child("1.2", "Sub Two", [TreeMap.new("1.2.1", "Some other SubSub")])
      ...> TreeMap.transduce(root, [], fn a, x -> a ++ [x.value] end, fn n, acc -> [n | acc] end) 
      ["Root", "Sub One", "SomeSubSub", "Sub Two", "Some other SubSub"]

      iex> root = TreeMap.new("1", "Root") 
      ...>        |> TreeMap.add_child("1.1", "Sub One", [TreeMap.new("1.1.1", "SomeSubSub")]) 
      ...>        |> TreeMap.add_child("1.2", "Sub Two", [TreeMap.new("1.2.1", "Some other SubSub")])
      ...> TreeMap.transduce(root, [], fn a, x -> a ++ [x] end, fn n, acc -> [n | acc] end) 
      ...> |> Enum.map(& &1.key)
      ["1", "1.1", "1.1.1", "1.2", "1.2.1"]
  """
  def transduce(tree_map, acc, modifier, reducer) do
    Enum.reduce(Enum.reverse(tree_map.children), modifier.(acc, tree_map), fn n, a ->
      transduce(n, a, modifier, reducer)
    end)
  end

  @doc """
  Find a node by it's key or returns `false` in a given tree.
  """
  def find(tree_map, key) do
    transduce(tree_map, nil, fn a, n -> a || (n.key == key && n) end, fn n, acc ->
      (n.key == key && n) || acc
    end)
  end

  @doc """
  Find a node by it's key or returns `false` in all root nodes.
  """
  def find(key) do
    list_roots()
    |> Enum.reduce(false, fn node, found ->
      found || find(node, key)
    end)
  end

  @doc """
  Create a Node and start a RootServer for it.
  """
  def start_root_node(key, value) do
    node = new(key, value)
    RootServer.start_child(node)
  end

  @doc """
  Start a root_node for the given `%Node{}`.
  """
  def start_root_node(node) do
    RootServer.start_child(node)
  end

  @doc """
  Retreive the value of a given root node.
  """
  def value(root_pid) do
    Node.value(root_pid)
  end

  @doc """
  List all root nodes from all RootServers.
  """
  def list_roots() do
    RootServer.list_roots()
  end

  @doc """
  Kill all RootServers.
  """
  def drop_all() do
    RootServer.drop_all()
  end
end