defmodule Algae.Tree.BinarySearch do @moduledoc """ Represent a `BinarySearch` tree. ## Examples iex> alias Algae.Tree.BinarySearch, as: BSTree ...> ...> BSTree.Node.new( ...> 42, ...> BSTree.Node.new(77), ...> BSTree.Node.new( ...> 1234, ...> BSTree.Node.new(98), ...> BSTree.Node.new(32) ...> ) ...> ) %Algae.Tree.BinarySearch.Node{ node: 42, left: %Algae.Tree.BinarySearch.Node{ node: 77, left: %Algae.Tree.BinarySearch.Empty{}, right: %Algae.Tree.BinarySearch.Empty{} }, right: %Algae.Tree.BinarySearch.Node{ node: 1234, left: %Algae.Tree.BinarySearch.Node{ node: 98, left: %Algae.Tree.BinarySearch.Empty{}, right: %Algae.Tree.BinarySearch.Empty{} }, right: %Algae.Tree.BinarySearch.Node{ node: 32, left: %Algae.Tree.BinarySearch.Empty{}, right: %Algae.Tree.BinarySearch.Empty{} } } } """ alias __MODULE__ alias BinarySearch.{Empty, Node} import Algae use Witchcraft, except: [to_list: 1] defsum do defdata(Empty :: none()) defdata Node do node :: any() left :: BinarySearch.t() \\ BinarySearch.Empty.new() right :: BinarySearch.t() \\ BinarySearch.Empty.new() end end @doc """ Create an empty tree. ## Examples iex> new() %Algae.Tree.BinarySearch.Empty{} """ @spec new() :: Empty.t() def new, do: %Empty{} @doc """ Bring a value into an otherwise empty tree. ## Examples iex> new(42) %Algae.Tree.BinarySearch.Node{ node: 42, left: %Algae.Tree.BinarySearch.Empty{}, right: %Algae.Tree.BinarySearch.Empty{} } """ @spec new(any()) :: Node.t() def new(value), do: %Node{node: value} @doc """ Insert a new element into a tree. ## Examples iex> insert(new(42), 43) %Algae.Tree.BinarySearch.Node{ node: 42, right: %Algae.Tree.BinarySearch.Node{ node: 43 } } """ @spec insert(t(), any()) :: t() def insert(%Empty{}, value), do: new(value) def insert(tree = %Node{node: node, left: left, right: right}, orderable) do case compare(orderable, node) do :equal -> tree :greater -> %{tree | right: insert(right, orderable)} :lesser -> %{tree | left: insert(left, orderable)} end end def insert(%Empty{}, value), do: new(value) def insert(tree = %Node{node: node, left: left, right: right}, orderable) do case compare(orderable, node) do :equal -> tree :greater -> %{tree | right: insert(right, orderable)} :lesser -> %{tree | left: insert(left, orderable)} end end @doc """ Remove an element from a tree by value. ## Examples iex> alias Algae.Tree.BinarySearch, as: BSTree ...> ...> BSTree.Node.new( ...> 42, ...> BSTree.Node.new(77), ...> BSTree.Node.new( ...> 1234, ...> BSTree.Node.new(98), ...> BSTree.Node.new(32) ...> ) ...> ) |> delete(98) %Algae.Tree.BinarySearch.Node{ node: 42, left: %Algae.Tree.BinarySearch.Node{ node: 77 }, right: %Algae.Tree.BinarySearch.Node{ node: 1234, right: %Algae.Tree.BinarySearch.Node{ node: 32 } } } """ @spec delete(t(), any()) :: t() def delete(%Empty{}, _), do: %Empty{} def delete(tree = %Node{node: node, left: left, right: right}, orderable) do case compare(orderable, node) do :greater -> %{tree | right: delete(right, orderable)} :lesser -> %{tree | left: delete(left, orderable)} :equal -> case tree do %{left: %Empty{}} -> right %{right: %Empty{}} -> left %{right: %{node: shift}} -> %{tree | node: shift, right: delete(right, shift)} end end end @doc """ Flatten a tree into a list. ## Examples iex> alias Algae.Tree.BinarySearch, as: BSTree ...> ...> BSTree.Node.new( ...> 42, ...> BSTree.Node.new(77), ...> BSTree.Node.new( ...> 1234, ...> BSTree.Node.new(98), ...> BSTree.Node.new(32) ...> ) ...> ) ...> |> BSTree.to_list() [42, 77, 1234, 98, 32] """ @spec to_list(t()) :: list() def to_list(tree), do: Witchcraft.Foldable.to_list(tree) @doc """ Flatten a tree into a list with elements sorted. ## Examples iex> alias Algae.Tree.BinarySearch, as: BSTree ...> ...> BSTree.Node.new( ...> 42, ...> BSTree.Node.new(77), ...> BSTree.Node.new( ...> 1234, ...> BSTree.Node.new(98), ...> BSTree.Node.new(32) ...> ) ...> ) ...> |> BSTree.to_ordered_list() [32, 42, 77, 98, 1234] """ @spec to_ordered_list(t()) :: list() def to_ordered_list(tree), do: tree |> to_list() |> Enum.sort() @doc """ Build a `BinarySearch` tree from a list. ## Examples iex> Algae.Tree.BinarySearch.from_list([42, 77, 1234, 98, 32]) %Algae.Tree.BinarySearch.Node{ node: 42, left: %Algae.Tree.BinarySearch.Node{ node: 32 }, right: %Algae.Tree.BinarySearch.Node{ node: 77, right: %Algae.Tree.BinarySearch.Node{ node: 1234, left: %Algae.Tree.BinarySearch.Node{ node: 98 } } } } """ @spec from_list(list()) :: t() def from_list([]), do: %Empty{} def from_list([head | tail]), do: from_list(tail, new(head)) @doc """ Build a `BinarySearch` tree from a list and attach to an existing tree. ## Examples iex> Algae.Tree.BinarySearch.from_list([42, 77, 1234, 98, 32], new(-9)) %Algae.Tree.BinarySearch.Node{ node: -9, right: %Algae.Tree.BinarySearch.Node{ left: %Algae.Tree.BinarySearch.Node{ node: 32 }, node: 42, right: %Algae.Tree.BinarySearch.Node{ node: 77, right: %Algae.Tree.BinarySearch.Node{ node: 1234, left: %Algae.Tree.BinarySearch.Node{ node: 98 }, right: %Algae.Tree.BinarySearch.Empty{} } } } } """ @spec from_list(list(), t()) :: t() def from_list([], seed), do: seed def from_list([head | tail], seed), do: from_list(tail, insert(seed, head)) end