heap/lib/heap.ex
2018-09-04 16:52:19 +12:00

261 lines
6.1 KiB
Elixir

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