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
*.ez
/doc
/docs

View file

@ -1,8 +1,14 @@
language: elixir
elixir:
- 1.2.2
- 1.5.1
otp_release:
- 18.2.1
script: mix espec
- 20.0
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:
- 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`:
def deps do
[{:heap, "~> 1.0.1"}]
[{:heap, "~> 1.1.0"}]
end
## Examples

View file

@ -12,37 +12,93 @@ defmodule Heap do
Create an empty min heap.
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
def min(), do: Heap.new(:>)
@spec min() :: t
def min, do: Heap.new(:>)
@doc """
Create an empty max heap.
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
def max(), do: Heap.new(:<)
@spec max() :: t
def max, do: Heap.new(:<)
@doc """
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(:> | :<) :: Heap.t
@spec new() :: t
@spec new(:> | :<) :: t
def new(direction \\ :>), do: %Heap{d: direction}
@doc """
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{}), do: false
@doc """
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()
def member?(%Heap{}=heap, value) do
@spec member?(t, any()) :: boolean()
def member?(%Heap{} = heap, value) do
root = Heap.root heap
heap = Heap.pop heap
has_member? heap, root, value
@ -50,50 +106,86 @@ defmodule Heap do
@doc """
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}
@doc """
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: {_, q}, n: n, d: d}), do: %Heap{h: pair(q, d), n: n - 1, d: d}
@doc """
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: nil, n: 0}), do: nil
@doc """
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
@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 sort(Enum.t, :< | :>) :: List.t
def sort(enum, direction \\ :>) do
enum
|> Enum.into(Heap.new(direction))
|> Enum.to_list
end
@spec comparator(t) :: :< | :>
def comparator(%Heap{d: d}), do: d
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({_,_}=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}, {k1, _} = r, :<) when k0 > k1, do: {k0, [r | l0]}
defp meld({_, _} = l, {k1, r0}, :<), do: {k1, [l | r0]}
defp pair([], _), do: nil
defp pair([q], _), do: q

View file

@ -1,8 +1,20 @@
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
(h, {:cont, v}) -> Heap.push h, v
(h, {:cont, v}) -> Heap.push(h, v)
(h, :done) -> h
(_, :halt) -> :ok
end}

View file

@ -1,23 +1,63 @@
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}
end
def member? heap, v do
{:ok, Heap.member?(heap, v)}
@doc """
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
@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(heap, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(heap, &1, fun)}
def reduce(heap, {:cont, acc}, fun) do
case Heap.root heap do
case Heap.root(heap) do
nil ->
{:done, acc}
root ->
heap = Heap.pop heap
heap = Heap.pop(heap)
reduce(heap, fun.(root, acc), fun)
end
end
end
end

View file

@ -1,11 +1,18 @@
defimpl Inspect, for: Heap do
import Inspect.Algebra
def inspect heap, opts do
concat [
"#Heap<",
to_doc(Enum.to_list(heap), opts),
">"
]
@doc """
Format the heap nicely for inspection.
## 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

26
mix.exs
View file

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

View file

@ -1,3 +1,7 @@
%{"espec": {:hex, :espec, "0.8.15", "a3427bc1e7984caa5f23dc25316e869260b10c2336bf5bb2b928638ad224c7a8", [:mix], [{:meck, "~> 0.8.4", [hex: :meck, optional: false]}]},
"ex_doc": {:hex, :ex_doc, "0.11.4", "a064bdb720594c3745b94709b17ffb834fd858b4e0c1f48f37c0d92700759e02", [:mix], [{:earmark, "~> 0.1.17 or ~> 0.2", [hex: :earmark, optional: true]}]},
"meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []}}
%{"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"credo": {:hex, :credo, "0.8.6", "335f723772d35da499b5ebfdaf6b426bfb73590b6fcbc8908d476b75f8cbca3f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
"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()