lib/data_structures/binary_search_tree.ex

defmodule BST do
  @moduledoc """
  Documentation for `Binary search tree` data structure.
  """

  @doc """
  Create a new Binary Search Tree with root's value as the given 'key'

  ## Examples

      iex> BST.new(5)
      %{key: 5, left: nil, right: nil, parent: nil}
      
  """
  def new(key, partner_key \\ nil), do: %{key: key, left: nil, right: nil, parent: partner_key}

  @doc """
  Search a specific node by given searched_key in given subtree (or a whole tree)
   - Average time complexity: O(log n)
   - Worst time complexity: O(n)

  ## Examples

      iex> BST.insert(nil, 2)
      %{key: 2, left: nil, right: nil, parent: nil}
      
  """
  def search(subtree, searched_key) do
    cond do
      subtree == nil || searched_key == subtree.key -> subtree
      searched_key < subtree.key -> search(subtree.left, searched_key)
      true -> search(subtree.right, searched_key)
    end
  end

  @doc """
  Insert a new node to the given tree.
   - Average time complexity: O(log n)
   - Worst time complexity:	O(n)

  ## Examples

      iex> BST.insert(nil, 2)
      %{key: 2, left: nil, right: nil, parent: nil}
      
  """
  def insert(nil, new_key), do: new(new_key)
  def insert(%{key: k} = tree, new_key) when new_key < k, do: update_in(tree.left, &insert_priv(&1, new_key, tree.key))
  def insert(tree, new_key), do: update_in(tree.right, &insert_priv(&1, new_key, tree.key))
  defp insert_priv(nil, new_key, parent_key), do: new(new_key, parent_key)

  @doc """
  Returns a minimum node of a given subtree

  ## Examples

      iex> BST.insert(nil, 2) |> BST.insert(5) |> BST.insert(1) |> BST.minimum
      %{key: 1, left: nil, right: nil, parent: 2}
      
  """
  def minimum(subtree) do
    if subtree.left == nil, do: subtree, else: minimum(subtree.left)
  end

  @doc """
  Returns a maximum node of a given subtree

  ## Examples

      iex> BST.insert(nil, 2) |> BST.insert(5) |> BST.insert(1) |> BST.maximum
      %{key: 5, left: nil, right: nil, parent: 2}
      
  """
  def maximum(subtree) do
    if subtree.right == nil, do: subtree, else: maximum(subtree.right)
  end
end