Modernise Elixir usage and replace espec with exunit.

This commit is contained in:
James Harton 2017-09-20 14:12:36 +12:00
parent 7ad923335f
commit 7ca833dd84
19 changed files with 237 additions and 254 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@
erl_crash.dump erl_crash.dump
*.ez *.ez
/doc /doc
/docs

View file

@ -1,8 +1,14 @@
language: elixir language: elixir
elixir: elixir:
- 1.2.2 - 1.5.1
otp_release: otp_release:
- 18.2.1 - 20.0
script: mix espec script:
- mix test
- mix credo --strict
- MIX_ENV=docs mix inch
after_script:
- mix deps.get --only docs
- MIX_ENV=docs mix inch.report
env: env:
- MIX_ENV=test - MIX_ENV=test

View file

@ -20,7 +20,7 @@ If [available in Hex](https://hex.pm/docs/publish), the package can be installed
1. Add heap to your list of dependencies in `mix.exs`: 1. Add heap to your list of dependencies in `mix.exs`:
def deps do def deps do
[{:heap, "~> 1.0.1"}] [{:heap, "~> 1.1.0"}]
end end
## Examples ## Examples

View file

@ -12,37 +12,93 @@ defmodule Heap do
Create an empty min heap. Create an empty min heap.
A min heap is a heap tree which always has the smallest value at the root. A min heap is a heap tree which always has the smallest value at the root.
## Examples
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.min())
...> |> Heap.root()
1
""" """
@spec min() :: Heap.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.
A max heap is a heap tree which always has the largest value at the root. A max heap is a heap tree which always has the largest value at the root.
## Examples
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.max())
...> |> Heap.root()
10
""" """
@spec max() :: Heap.t @spec max() :: t
def max(), do: Heap.new(:<) def max, do: Heap.new(:<)
@doc """ @doc """
Create an empty heap. Create an empty heap.
## Examples
Create an empty heap with the default direction.
iex> Heap.new()
...> |> Heap.comparator()
:>
Create an empty heap with a specific direction.
iex> Heap.new(:<)
...> |> Heap.comparator()
:<
""" """
@spec new() :: Heap.t @spec new() :: t
@spec new(:> | :<) :: Heap.t @spec new(:> | :<) :: t
def new(direction \\ :>), do: %Heap{d: direction} def new(direction \\ :>), do: %Heap{d: direction}
@doc """ @doc """
Test if the heap is empty. Test if the heap is empty.
## Examples
iex> Heap.new()
...> |> Heap.empty?()
true
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.empty?()
false
""" """
@spec empty?(Heap.t) :: boolean() @spec empty?(t) :: boolean()
def empty?(%Heap{h: nil, n: 0}), do: true def empty?(%Heap{h: nil, n: 0}), do: true
def empty?(%Heap{}), do: false def empty?(%Heap{}), do: false
@doc """ @doc """
Test if the heap contains the element <elem> Test if the heap contains the element <elem>
## Examples
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.member?(11)
false
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.member?(7)
true
""" """
@spec member?(Heap.t, any()) :: boolean() @spec member?(t, any()) :: boolean()
def member?(%Heap{}=heap, value) do def member?(%Heap{} = heap, value) do
root = Heap.root heap root = Heap.root heap
heap = Heap.pop heap heap = Heap.pop heap
has_member? heap, root, value has_member? heap, root, value
@ -50,50 +106,86 @@ defmodule Heap do
@doc """ @doc """
Push a new element into the heap. Push a new element into the heap.
## Examples
iex> Heap.new()
...> |> Heap.push(13)
...> |> Heap.root()
13
""" """
@spec push(Heap.t, any()) :: Heap.t @spec push(t, any()) :: t
def push(%Heap{h: h, n: n, d: d}, v), do: %Heap{h: meld(h, {v, []}, d), n: n + 1, d: d} def push(%Heap{h: h, n: n, d: d}, v), do: %Heap{h: meld(h, {v, []}, d), n: n + 1, d: d}
@doc """ @doc """
Pop the root element off the heap and discard it. Pop the root element off the heap and discard it.
## Examples
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.pop()
...> |> Heap.root()
2
""" """
@spec pop(Heap.t) :: Heap.t @spec pop(t) :: t
def pop(%Heap{h: nil, n: 0}), do: nil def pop(%Heap{h: nil, n: 0}), do: nil
def pop(%Heap{h: {_, q}, n: n, d: d}), do: %Heap{h: pair(q, d), n: n - 1, d: d} def pop(%Heap{h: {_, q}, n: n, d: d}), do: %Heap{h: pair(q, d), n: n - 1, d: d}
@doc """ @doc """
Return the element at the root of the heap. Return the element at the root of the heap.
## Examples
iex> Heap.new()
...> |> Heap.root()
nil
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.root()
1
""" """
@spec root(Heap.t) :: any() @spec root(t) :: any()
def root(%Heap{h: {v, _}}), do: v def root(%Heap{h: {v, _}}), do: v
def root(%Heap{h: nil, n: 0}), do: nil def root(%Heap{h: nil, n: 0}), do: nil
@doc """ @doc """
Return the number of elements in the heap. Return the number of elements in the heap.
## Examples
iex> 1..10
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.size()
10
""" """
@spec size(Heap.t) :: non_neg_integer() @spec size(t) :: non_neg_integer()
def size(%Heap{n: n}), do: n def size(%Heap{n: n}), do: n
@doc """ @doc """
Quickly sort an enumerable with a heap. Return the comparator of the heap.
## Examples
iex> Heap.new(:<)
...> |> Heap.comparator()
:<
""" """
@spec sort(Enum.t) :: List.t @spec comparator(t) :: :< | :>
@spec sort(Enum.t, :< | :>) :: List.t def comparator(%Heap{d: d}), do: d
def sort(enum, direction \\ :>) do
enum
|> Enum.into(Heap.new(direction))
|> Enum.to_list
end
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({_, _} = 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 pair([], _), do: nil defp pair([], _), do: nil
defp pair([q], _), do: q defp pair([q], _), do: q

View file

@ -1,8 +1,20 @@
defimpl Collectable, for: Heap do defimpl Collectable, for: Heap do
def into heap do @doc """
Collect an enumerable into a heap.
## Examples
iex> 1..500
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Heap.root
1
"""
@spec into(Heap.t) :: {list, function}
def into(heap) do
{heap, fn {heap, fn
(h, {:cont, v}) -> Heap.push h, v (h, {:cont, v}) -> Heap.push(h, v)
(h, :done) -> h (h, :done) -> h
(_, :halt) -> :ok (_, :halt) -> :ok
end} end}

View file

@ -1,23 +1,63 @@
defimpl Enumerable, for: Heap do defimpl Enumerable, for: Heap do
def count heap do @doc """
Returns the number of elements in a heap.
## Examples
iex> 1..500
...> |> Enum.into(Heap.new())
...> |> Enum.count()
500
"""
@spec count(Heap.t) :: non_neg_integer
def count(heap) do
{:ok, Heap.size heap} {:ok, Heap.size heap}
end end
def member? heap, v do @doc """
{:ok, Heap.member?(heap, v)} Returns true if value is a contained in the heap.
## Examples
iex> 1..500
...> |> Enum.into(Heap.new())
...> |> Enum.member?(250)
true
iex> 1..500
...> |> Enum.into(Heap.new())
...> |> Enum.member?(750)
false
"""
@spec member?(Heap.t, term) :: boolean
def member?(heap, value) do
{:ok, Heap.member?(heap, value)}
end end
@doc """
Allows reduction to be applied to Heaps.
## Examples
iex> 1..500
...> |> Enum.shuffle()
...> |> Enum.into(Heap.new())
...> |> Enum.filter(&(Integer.mod(&1, 2) == 0))
...> |> Enum.count()
250
"""
@spec reduce(Heap.t, Enumerable.acc, Enumerable.reducer) :: Enumerable.result
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc} def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(heap, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(heap, &1, fun)} def reduce(heap, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(heap, &1, fun)}
def reduce(heap, {:cont, acc}, fun) do def reduce(heap, {:cont, acc}, fun) do
case Heap.root heap do case Heap.root(heap) do
nil -> nil ->
{:done, acc} {:done, acc}
root -> root ->
heap = Heap.pop heap heap = Heap.pop(heap)
reduce(heap, fun.(root, acc), fun) reduce(heap, fun.(root, acc), fun)
end end
end end
end end

View file

@ -1,11 +1,18 @@
defimpl Inspect, for: Heap do defimpl Inspect, for: Heap do
import Inspect.Algebra import Inspect.Algebra
def inspect heap, opts do @doc """
concat [ Format the heap nicely for inspection.
"#Heap<",
to_doc(Enum.to_list(heap), opts), ## Examples
">"
] iex> 1..10
...> |> Enum.into(Heap.max())
...> |> inspect
"#Heap<[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]>"
"""
@spec inspect(Heap.t, Inspect.Opts.t) :: Inspect.Algebra.t
def inspect(heap, opts) do
concat(["#Heap<", to_doc(Enum.to_list(heap), opts), ">"])
end end
end end

26
mix.exs
View file

@ -3,14 +3,13 @@ defmodule Heap.Mixfile do
def project do def project do
[app: :heap, [app: :heap,
version: "1.0.1", version: "1.1.0",
description: description, description: description(),
elixir: "~> 1.2", elixir: "~> 1.5",
build_embedded: Mix.env == :prod, build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod, start_permanent: Mix.env == :prod,
preferred_cli_env: [espec: :test], package: package(),
package: package, deps: deps()]
deps: deps]
end end
# Configuration for the OTP application # Configuration for the OTP application
@ -21,18 +20,12 @@ defmodule Heap.Mixfile do
end end
def description do def description do
""" "Small composable Heap implementation. Heaps sort elements at insert time."
Small composable Heap implementation. Heaps sort elements at insert time.
They're good for:
* Scientific computing
* Statistics
* Priority queues
"""
end end
def package do def package do
[ [
maintainers: [ "James Harton <james@messagerocket.co>" ], maintainers: [ "James Harton <james@automat.nz>" ],
licenses: [ "MIT" ], licenses: [ "MIT" ],
links: %{ links: %{
"GitHub" => "https://github.com/jamesotron/heap", "GitHub" => "https://github.com/jamesotron/heap",
@ -52,8 +45,9 @@ defmodule Heap.Mixfile do
# Type "mix help deps" for more examples and options # Type "mix help deps" for more examples and options
defp deps do defp deps do
[ [
{:espec, "~> 0.8.11", only: :test}, {:ex_doc, ">= 0.0.0", only: :dev},
{:ex_doc, "~> 0.11.4", only: :dev} {:credo, "~> 0.6", only: ~w(dev test)a},
{:inch_ex, "~> 0.5", only: :docs}
] ]
end end
end end

View file

@ -1,3 +1,7 @@
%{"espec": {:hex, :espec, "0.8.15", "a3427bc1e7984caa5f23dc25316e869260b10c2336bf5bb2b928638ad224c7a8", [:mix], [{:meck, "~> 0.8.4", [hex: :meck, optional: false]}]}, %{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"ex_doc": {:hex, :ex_doc, "0.11.4", "a064bdb720594c3745b94709b17ffb834fd858b4e0c1f48f37c0d92700759e02", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]}, "credo": {:hex, :credo, "0.8.6", "335f723772d35da499b5ebfdaf6b426bfb73590b6fcbc8908d476b75f8cbca3f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
"meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []}} "espec": {:hex, :espec, "0.8.28", "f002710673d215876c4ca6fc74cbf5e330954badea7389d2284d2050940f1779", [:mix], [{:meck, "~> 0.8.4", [hex: :meck, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.11.5", "0dc51cb84f8312162a2313d6c71573a9afa332333d8a332bb12540861b9834db", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]},
"inch_ex": {:hex, :inch_ex, "0.5.6", "418357418a553baa6d04eccd1b44171936817db61f4c0840112b420b8e378e67", [:mix], [{:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: false]}]},
"meck": {:hex, :meck, "0.8.8", "eeb3efe811d4346e1a7f65b2738abc2ad73cbe1a2c91b5dd909bac2ea0414fa6", [:rebar3], []},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}}

View file

@ -1,12 +0,0 @@
defmodule HeapCollectableSpec do
use ESpec
describe "into" do
let :source, do: 1..500
subject do: source |> Enum.into(Heap.new)
it "inserts into the heap" do
expect(Heap.size subject) |> to(eq 500)
end
end
end

View file

@ -1,34 +0,0 @@
defmodule HeapEnumerableSpec do
use ESpec
describe "count" do
let :heap, do: Enum.into(1..500, Heap.new)
subject do: Enum.count heap
it do: is_expected |> to(eq 500)
end
describe "member?" do
let :heap, do: Heap.new
subject do: Enum.member? heap, 13
context "When the value is in the heap" do
let :heap, do: Heap.new |> Heap.push(13)
it do: is_expected |> to(be_true)
end
context "When the value is not in the heap" do
it do: is_expected |> to(be_false)
end
end
describe "reduce" do
let :heap, do: Enum.into(1..500, Heap.new)
subject do: Enum.filter heap, fn(i) -> rem(i, 2) == 0 end
it do: is_expected |> to(have_min 2)
it do: is_expected |> to(have_max 500)
it do: is_expected |> to(have_count 250)
end
end

View file

@ -1,10 +0,0 @@
defmodule HeapInspectSpec do
use ESpec
describe "inspect" do
let :heap, do: Enum.into(1..5, Heap.new)
subject do: inspect heap
it do: is_expected |> to(eq "#Heap<[1, 2, 3, 4, 5]>")
end
end

View file

@ -1,123 +0,0 @@
defmodule HeapSpec do
use ESpec
describe "new" do
subject do: Heap.new
it do: is_expected |> to(be_struct Heap)
it "is empty" do
expect(Heap.empty? subject) |> to(be_true)
end
end
describe "min" do
subject do: Heap.min
it do: is_expected |> to(be_struct Heap)
it "is empty" do
expect(Heap.empty? subject) |> to(be_true)
end
it "has the correct sort direction" do
expect(subject.d) |> to(eq :>)
end
end
describe "max" do
subject do: Heap.max
it do: is_expected |> to(be_struct Heap)
it "is empty" do
expect(Heap.empty? subject) |> to(be_true)
end
it "has the correct sort direction" do
expect(subject.d) |> to(eq :<)
end
end
describe "empty?" do
subject do: Heap.empty? heap
context "When the heap is empty" do
let :heap, do: Heap.new
it do: is_expected |> to(be_true)
end
context "When the heap contains elements" do
let :heap, do: Heap.new |> Heap.push(13)
it do: is_expected |> to(be_false)
end
end
describe "member?" do
subject do: heap |> Heap.member?(13)
context "When the heap contains the element" do
let :heap, do: Heap.new |> Heap.push(13)
it do: is_expected |> to(be_true)
end
context "When the heap does not contain the element" do
let :heap, do: Heap.new
it do: is_expected |> to(be_false)
end
end
describe "push" do
let :heap, do: Heap.new
subject do: heap |> Heap.push(13)
it "adds the element to the heap" do
expect(subject |> Heap.member?(13)) |> to(be_true)
end
end
describe "pop" do
let :heap, do: Heap.new |> Heap.push(13) |> Heap.push(14)
subject do: Heap.pop heap
it "pops the root element off the heap" do
expect(Heap.root heap) |> to(eq 13)
expect(Heap.root subject) |> to(eq 14)
end
end
describe "root" do
let :heap, do: Heap.new |> Heap.push(13)
subject do: Heap.root heap
it do: is_expected |> to(eq 13)
end
describe "size" do
let :heap, do: Heap.new |> Heap.push(13) |> Heap.push(14)
subject do: Heap.size heap
it do: is_expected |> to(eq 2)
end
describe "sort" do
let :source, do: Enum.shuffle(1..500)
subject do: Heap.sort source, direction
context "When the direction is `:>`" do
let :direction, do: :>
it do: is_expected |> to(eq Enum.to_list(1..500))
end
context "When the direction is `:<`" do
let :direction, do: :<
it do: is_expected |> to(eq Enum.to_list(1..500) |> Enum.reverse)
end
end
end

View file

@ -1,11 +0,0 @@
ESpec.start
ESpec.configure fn(config) ->
config.before fn ->
{:shared, hello: :world}
end
config.finally fn(_shared) ->
:ok
end
end

View file

@ -0,0 +1,4 @@
defmodule CollectableHeapTest do
use ExUnit.Case
doctest Collectable.Heap
end

View file

@ -0,0 +1,4 @@
defmodule EnumerableHeapTest do
use ExUnit.Case
doctest Enumerable.Heap
end

View file

@ -0,0 +1,4 @@
defmodule InspectHeapTest do
use ExUnit.Case
doctest Inspect.Heap
end

4
test/heap_test.exs Normal file
View file

@ -0,0 +1,4 @@
defmodule HeapTest do
use ExUnit.Case
doctest Heap
end

1
test/test_helper.exs Normal file
View file

@ -0,0 +1 @@
ExUnit.start()