diff --git a/README.md b/README.md index 038fd1b..3484ec6 100644 --- a/README.md +++ b/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] diff --git a/lib/heap.ex b/lib/heap.ex index 5925907..cf454d0 100644 --- a/lib/heap.ex +++ b/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 + ## 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) 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 diff --git a/mix.exs b/mix.exs index 3b78b82..3b12b24 100644 --- a/mix.exs +++ b/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,