Adds support for custom comparators and Heap.split/1

This commit is contained in:
Rainer Dreyer 2017-09-22 18:13:21 -04:00
parent 70f02120a4
commit 1d1abd5f0f
2 changed files with 69 additions and 9 deletions

View file

@ -56,6 +56,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

@ -57,19 +57,40 @@ defmodule Heap do
@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
## Examples
iex> Heap.new(:<)
...> |> Heap.comparator()
:<
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), do: %Heap{comparator: fun}
@doc """
Test if `heap` is empty.
@ -187,6 +208,19 @@ 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
@ -196,6 +230,13 @@ defmodule Heap do
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) do
case fun.(k0, k1) do
true -> {k0, [r | l0]}
false -> {k1, [l | r0]}
end
end
defp pair([], _), do: nil
defp pair([q], _), do: q
defp pair([q0, q1 | q], d) do
@ -206,8 +247,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