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
|
## 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`:
|
1. Add heap to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
def deps do
|
def deps do
|
||||||
[{:heap, "~> 1.1.0"}]
|
[{:heap, "~> 2.0"}]
|
||||||
end
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run `mix deps.get`
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -56,6 +60,26 @@ Heap.new
|
||||||
# => [:bread, :milk, :coffee, :eggs, :butter, :jam]
|
# => [: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
|
### Documentation
|
||||||
|
|
||||||
Full API documentation is available on (hexdocs.pm)[https://hexdocs.pm/heap]
|
Full API documentation is available on (hexdocs.pm)[https://hexdocs.pm/heap]
|
||||||
|
|
73
lib/heap.ex
73
lib/heap.ex
|
@ -22,7 +22,7 @@ defmodule Heap do
|
||||||
1
|
1
|
||||||
"""
|
"""
|
||||||
@spec min() :: t
|
@spec min() :: t
|
||||||
def min, do: Heap.new(:>)
|
def min, do: Heap.new(:<)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an empty max `Heap`.
|
Create an empty max `Heap`.
|
||||||
|
@ -38,7 +38,7 @@ defmodule Heap do
|
||||||
10
|
10
|
||||||
"""
|
"""
|
||||||
@spec max() :: t
|
@spec max() :: t
|
||||||
def max, do: Heap.new(:<)
|
def max, do: Heap.new(:>)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an empty `Heap` with the default comparator (`>`).
|
Create an empty `Heap` with the default comparator (`>`).
|
||||||
|
@ -49,27 +49,48 @@ defmodule Heap do
|
||||||
|
|
||||||
iex> Heap.new()
|
iex> Heap.new()
|
||||||
...> |> Heap.comparator()
|
...> |> Heap.comparator()
|
||||||
:>
|
:<
|
||||||
"""
|
"""
|
||||||
@spec new() :: t
|
@spec new() :: t
|
||||||
def new, do: %Heap{comparator: :>}
|
def new, do: %Heap{comparator: :<}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an empty heap with a specific comparator.
|
Create an empty heap with a specific comparator.
|
||||||
|
|
||||||
Provide a `comparator` option, which can be either `:<` or `:>` to indicate
|
Provide a `comparator` option, which can be `:<`, `:>` to indicate
|
||||||
that the `Heap` should use Elixir's normal `<` or `>` comparison functions.
|
that the `Heap` should use Elixir's normal `<` or `>` comparison functions
|
||||||
|
or a custom comparator function.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> Heap.new(:<)
|
iex> Heap.new(:<)
|
||||||
...> |> Heap.comparator()
|
...> |> 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
|
@spec new(:> | :<) :: t
|
||||||
def new(:>), do: %Heap{comparator: :>}
|
def new(:>), do: %Heap{comparator: :>}
|
||||||
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 """
|
@doc """
|
||||||
Test if `heap` is empty.
|
Test if `heap` is empty.
|
||||||
|
|
||||||
|
@ -187,14 +208,35 @@ defmodule Heap do
|
||||||
@spec comparator(t) :: :< | :>
|
@spec comparator(t) :: :< | :>
|
||||||
def comparator(%Heap{comparator: d}), do: d
|
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(nil, queue, _), do: queue
|
||||||
defp meld(queue, nil, _), 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({_, _} = l, {k1, r0}, :>), do: {k1, [l | r0]}
|
||||||
|
|
||||||
defp meld({k0, l0}, {k1, _} = r, :<) when k0 > k1, do: {k0, [r | l0]}
|
defp meld({k0, l0} = l, {k1, r0} = r, fun) when is_function(fun, 2) do
|
||||||
defp meld({_, _} = l, {k1, r0}, :<), do: {k1, [l | r0]}
|
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([], _), do: nil
|
||||||
defp pair([q], _), do: q
|
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?(_, previous, compare) when previous == compare, do: true
|
||||||
defp has_member?(nil, _, _), do: false
|
defp has_member?(nil, _, _), do: false
|
||||||
defp has_member?(heap, _, compare) do
|
defp has_member?(heap, _, compare) do
|
||||||
previous = Heap.root heap
|
{previous, heap} = Heap.split heap
|
||||||
heap = Heap.pop heap
|
|
||||||
has_member? heap, previous, compare
|
has_member? heap, previous, compare
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -3,7 +3,7 @@ defmodule Heap.Mixfile do
|
||||||
|
|
||||||
def project do
|
def project do
|
||||||
[app: :heap,
|
[app: :heap,
|
||||||
version: "1.1.0",
|
version: "2.0.0",
|
||||||
description: description(),
|
description: description(),
|
||||||
elixir: "~> 1.5",
|
elixir: "~> 1.5",
|
||||||
build_embedded: Mix.env == :prod,
|
build_embedded: Mix.env == :prod,
|
||||||
|
|
Loading…
Reference in a new issue