Merge pull request #3 from rdrey/master

Adds support for custom comparators and `Heap.split/1`
This commit is contained in:
James Harton 2017-09-26 13:33:02 +13:00 committed by GitHub
commit 6c7a7c45db
3 changed files with 84 additions and 19 deletions

View file

@ -15,13 +15,17 @@ You can use it for things like:
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
This package is [available in Hex](https://hex.pm/packages/heap):
1. Add heap to your list of dependencies in `mix.exs`:
```elixir
def deps do
[{:heap, "~> 1.1.0"}]
[{:heap, "~> 2.0"}]
end
```
2. Run `mix deps.get`
## Examples
@ -56,6 +60,26 @@ Heap.new
# => [:bread, :milk, :coffee, :eggs, :butter, :jam]
```
The heap can also be constructed with a custom comparator:
```elixir
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]
```
To access the root and the rest of the heap in one line use `Heap.split/1`:
```elixir
{root, rest} = Heap.split(heap)
{root, rest} == {Heap.root(heap), Heap.pop(heap)}
# => true
```
### Documentation
Full API documentation is available on (hexdocs.pm)[https://hexdocs.pm/heap]

View file

@ -22,7 +22,7 @@ defmodule Heap do
1
"""
@spec min() :: t
def min, do: Heap.new(:>)
def min, do: Heap.new(:<)
@doc """
Create an empty max `Heap`.
@ -38,7 +38,7 @@ defmodule Heap do
10
"""
@spec max() :: t
def max, do: Heap.new(:<)
def max, do: Heap.new(:>)
@doc """
Create an empty `Heap` with the default comparator (`>`).
@ -49,27 +49,48 @@ defmodule Heap do
iex> Heap.new()
...> |> Heap.comparator()
:>
:<
"""
@spec new() :: t
def new, do: %Heap{comparator: :>}
def new, do: %Heap{comparator: :<}
@doc """
Create an empty heap with a specific comparator.
Provide a `comparator` option, which can be either `:<` or `:>` to indicate
that the `Heap` should use Elixir's normal `<` or `>` comparison functions.
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.
@ -187,14 +208,35 @@ defmodule Heap do
@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({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}, {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
@ -206,8 +248,7 @@ defmodule Heap do
defp has_member?(_, previous, compare) when previous == compare, do: true
defp has_member?(nil, _, _), do: false
defp has_member?(heap, _, compare) do
previous = Heap.root heap
heap = Heap.pop heap
{previous, heap} = Heap.split heap
has_member? heap, previous, compare
end
end

View file

@ -3,7 +3,7 @@ defmodule Heap.Mixfile do
def project do
[app: :heap,
version: "1.1.0",
version: "2.0.0",
description: description(),
elixir: "~> 1.5",
build_embedded: Mix.env == :prod,