defmodule Heap do defstruct data: nil, size: 0, comparator: nil @moduledoc """ A heap is a special tree data structure. Good for sorting and other magic. See also: [Heap (data structure) on Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure)). """ @type t :: %Heap{} @doc """ Create an empty min `Heap`. A min heap is a heap tree which always has the smallest value at the root. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.min()) ...> |> Heap.root() 1 """ @spec min() :: t def min, do: Heap.new(:<) @doc """ Create an empty max `Heap`. A max heap is a heap tree which always has the largest value at the root. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.max()) ...> |> Heap.root() 10 """ @spec max() :: t def max, do: Heap.new(:>) @doc """ Create an empty `Heap` with the default comparator (`<`). Defaults to `>`. ## Examples iex> Heap.new() ...> |> Heap.comparator() :< """ @spec new() :: t def new, do: %Heap{comparator: :<} @doc """ Create an empty heap with a specific comparator. Provide a `comparator` option, which can be `:<`, `:>` to indicate that the `Heap` should use Elixir's normal `<` or `>` comparison functions or a custom comparator function. ## Examples iex> Heap.new(:<) ...> |> Heap.comparator() :< If given a function it should compare two arguments, and return `true` if the first argument precedes the second one. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new(&(&1 > &2))) ...> |> Enum.to_list() [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] iex> Heap.new(&(Date.compare(elem(&1, 0), elem(&2, 0)) == :gt)) ...> |> Heap.push({~D[2017-11-20], :jam}) ...> |> Heap.push({~D[2017-11-21], :milk}) ...> |> Heap.push({~D[2017-10-21], :bread}) ...> |> Heap.push({~D[2017-10-20], :eggs}) ...> |> Enum.map(fn {_, what} -> what end) [:milk, :jam, :bread, :eggs] """ @spec new(:> | :<) :: t def new(:>), do: %Heap{comparator: :>} def new(:<), do: %Heap{comparator: :<} @spec new((any, any -> boolean)) :: t def new(fun) when is_function(fun, 2), do: %Heap{comparator: fun} @doc """ Test if `heap` is empty. ## Examples iex> Heap.new() ...> |> Heap.empty?() true iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.empty?() false """ @spec empty?(t) :: boolean() def empty?(%Heap{data: nil, size: 0}), do: true def empty?(%Heap{}), do: false @doc """ Test if the `heap` contains the element `value`. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.member?(11) false iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.member?(7) true """ @spec member?(t, any()) :: boolean() def member?(%Heap{} = heap, value) do root = Heap.root(heap) heap = Heap.pop(heap) has_member?(heap, root, value) end @doc """ Push a new `value` into `heap`. ## Examples iex> Heap.new() ...> |> Heap.push(13) ...> |> Heap.root() 13 """ @spec push(t, any()) :: t def push(%Heap{data: h, size: n, comparator: d}, value), do: %Heap{data: meld(h, {value, []}, d), size: n + 1, comparator: d} @doc """ Pop the root element off `heap` and discard it. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.pop() ...> |> Heap.root() 2 """ @spec pop(t) :: t def pop(%Heap{data: nil, size: 0} = _heap), do: nil def pop(%Heap{data: {_, q}, size: n, comparator: d} = _heap), do: %Heap{data: pair(q, d), size: n - 1, comparator: d} @doc """ Return the element at the root of `heap`. ## Examples iex> Heap.new() ...> |> Heap.root() nil iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.root() 1 """ @spec root(t) :: any() def root(%Heap{data: {v, _}} = _heap), do: v def root(%Heap{data: nil, size: 0} = _heap), do: nil @doc """ Return the number of elements in `heap`. ## Examples iex> 1..10 ...> |> Enum.shuffle() ...> |> Enum.into(Heap.new()) ...> |> Heap.size() 10 """ @spec size(t) :: non_neg_integer() def size(%Heap{size: n}), do: n @doc """ Return the comparator `heap` is using for insert comparisons. ## Examples iex> Heap.new(:<) ...> |> Heap.comparator() :< """ @spec comparator(t) :: :< | :> def comparator(%Heap{comparator: d}), do: d @doc """ Return the root element and the rest of the heap in one operation. ## Examples iex> heap = 1..10 |> Enum.into(Heap.min()) ...> rest = Heap.pop(heap) ...> {1, rest} == Heap.split(heap) true """ @spec split(t) :: {any, t} def split(%Heap{} = heap), do: {Heap.root(heap), Heap.pop(heap)} defp meld(nil, queue, _), do: queue defp meld(queue, nil, _), do: queue defp meld({k0, l0}, {k1, _} = r, :<) when k0 < k1, do: {k0, [r | l0]} defp meld({_, _} = l, {k1, r0}, :<), do: {k1, [l | r0]} defp meld({k0, l0}, {k1, _} = r, :>) when k0 > k1, do: {k0, [r | l0]} defp meld({_, _} = l, {k1, r0}, :>), do: {k1, [l | r0]} defp meld({k0, l0} = l, {k1, r0} = r, fun) when is_function(fun, 2) do case fun.(k0, k1) do true -> {k0, [r | l0]} false -> {k1, [l | r0]} err -> raise("Comparator should return boolean, but returned '#{err}'.") end end defp pair([], _), do: nil defp pair([q], _), do: q defp pair([q0, q1 | q], d) do q2 = meld(q0, q1, d) meld(q2, pair(q, d), d) end defp has_member?(_, previous, compare) when previous == compare, do: true defp has_member?(nil, _, _), do: false defp has_member?(heap, _, compare) do {previous, heap} = Heap.split(heap) has_member?(heap, previous, compare) end end