From 7ca833dd843bbb54ab2f7807c05fbb85c5daa477 Mon Sep 17 00:00:00 2001 From: James Harton Date: Wed, 20 Sep 2017 14:12:36 +1200 Subject: [PATCH] Modernise Elixir usage and replace espec with exunit. --- .gitignore | 1 + .travis.yml | 12 ++- README.md | 2 +- lib/heap.ex | 144 +++++++++++++++++++++++++++------ lib/heap/collectable.ex | 16 +++- lib/heap/enumerable.ex | 52 ++++++++++-- lib/heap/inspect.ex | 21 +++-- mix.exs | 26 +++--- mix.lock | 10 ++- spec/heap/collectable_spec.exs | 12 --- spec/heap/enumerable_spec.exs | 34 -------- spec/heap/inspect_spec.exs | 10 --- spec/heap_spec.exs | 123 ---------------------------- spec/spec_helper.exs | 11 --- test/heap/collectable_test.exs | 4 + test/heap/enumerable_test.exs | 4 + test/heap/inspect_test.exs | 4 + test/heap_test.exs | 4 + test/test_helper.exs | 1 + 19 files changed, 237 insertions(+), 254 deletions(-) delete mode 100644 spec/heap/collectable_spec.exs delete mode 100644 spec/heap/enumerable_spec.exs delete mode 100644 spec/heap/inspect_spec.exs delete mode 100644 spec/heap_spec.exs delete mode 100644 spec/spec_helper.exs create mode 100644 test/heap/collectable_test.exs create mode 100644 test/heap/enumerable_test.exs create mode 100644 test/heap/inspect_test.exs create mode 100644 test/heap_test.exs create mode 100644 test/test_helper.exs diff --git a/.gitignore b/.gitignore index 0ff0618..45b9906 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ erl_crash.dump *.ez /doc +/docs diff --git a/.travis.yml b/.travis.yml index 5ed991f..ed51ce3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 8866fa2..038fd1b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/heap.ex b/lib/heap.ex index c4cb241..6cbce2d 100644 --- a/lib/heap.ex +++ b/lib/heap.ex @@ -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 + + ## 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 diff --git a/lib/heap/collectable.ex b/lib/heap/collectable.ex index 4e11807..107d30a 100644 --- a/lib/heap/collectable.ex +++ b/lib/heap/collectable.ex @@ -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} diff --git a/lib/heap/enumerable.ex b/lib/heap/enumerable.ex index a78820e..392ba44 100644 --- a/lib/heap/enumerable.ex +++ b/lib/heap/enumerable.ex @@ -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 \ No newline at end of file +end diff --git a/lib/heap/inspect.ex b/lib/heap/inspect.ex index 11036c9..baa6952 100644 --- a/lib/heap/inspect.ex +++ b/lib/heap/inspect.ex @@ -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 \ No newline at end of file +end diff --git a/mix.exs b/mix.exs index ba97cb4..3b78b82 100644 --- a/mix.exs +++ b/mix.exs @@ -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 " ], + maintainers: [ "James Harton " ], 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 diff --git a/mix.lock b/mix.lock index 24e13fc..51a555b 100644 --- a/mix.lock +++ b/mix.lock @@ -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], []}} diff --git a/spec/heap/collectable_spec.exs b/spec/heap/collectable_spec.exs deleted file mode 100644 index 2bba66f..0000000 --- a/spec/heap/collectable_spec.exs +++ /dev/null @@ -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 \ No newline at end of file diff --git a/spec/heap/enumerable_spec.exs b/spec/heap/enumerable_spec.exs deleted file mode 100644 index 8d857b5..0000000 --- a/spec/heap/enumerable_spec.exs +++ /dev/null @@ -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 \ No newline at end of file diff --git a/spec/heap/inspect_spec.exs b/spec/heap/inspect_spec.exs deleted file mode 100644 index 64ccb81..0000000 --- a/spec/heap/inspect_spec.exs +++ /dev/null @@ -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 \ No newline at end of file diff --git a/spec/heap_spec.exs b/spec/heap_spec.exs deleted file mode 100644 index 13db619..0000000 --- a/spec/heap_spec.exs +++ /dev/null @@ -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 \ No newline at end of file diff --git a/spec/spec_helper.exs b/spec/spec_helper.exs deleted file mode 100644 index d8e1d1b..0000000 --- a/spec/spec_helper.exs +++ /dev/null @@ -1,11 +0,0 @@ -ESpec.start - -ESpec.configure fn(config) -> - config.before fn -> - {:shared, hello: :world} - end - - config.finally fn(_shared) -> - :ok - end -end diff --git a/test/heap/collectable_test.exs b/test/heap/collectable_test.exs new file mode 100644 index 0000000..83314d4 --- /dev/null +++ b/test/heap/collectable_test.exs @@ -0,0 +1,4 @@ +defmodule CollectableHeapTest do + use ExUnit.Case + doctest Collectable.Heap +end diff --git a/test/heap/enumerable_test.exs b/test/heap/enumerable_test.exs new file mode 100644 index 0000000..2eabad4 --- /dev/null +++ b/test/heap/enumerable_test.exs @@ -0,0 +1,4 @@ +defmodule EnumerableHeapTest do + use ExUnit.Case + doctest Enumerable.Heap +end diff --git a/test/heap/inspect_test.exs b/test/heap/inspect_test.exs new file mode 100644 index 0000000..ce63117 --- /dev/null +++ b/test/heap/inspect_test.exs @@ -0,0 +1,4 @@ +defmodule InspectHeapTest do + use ExUnit.Case + doctest Inspect.Heap +end diff --git a/test/heap_test.exs b/test/heap_test.exs new file mode 100644 index 0000000..42d8cc8 --- /dev/null +++ b/test/heap_test.exs @@ -0,0 +1,4 @@ +defmodule HeapTest do + use ExUnit.Case + doctest Heap +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()