Merge pull request #3 from rdrey/master
Adds support for custom comparators and `Heap.split/1`
This commit is contained in:
commit
6c7a7c45db
3 changed files with 84 additions and 19 deletions
28
README.md
28
README.md
|
@ -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]
|
||||
|
|
65
lib/heap.ex
65
lib/heap.ex
|
@ -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
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue