Initial push of Heap
.
This commit is contained in:
commit
9d9b1c6045
14 changed files with 494 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/_build
|
||||
/cover
|
||||
/deps
|
||||
erl_crash.dump
|
||||
*.ez
|
66
README.md
Normal file
66
README.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Heap
|
||||
|
||||
A Heap is a very useful data structure, because it sorts, quickly, at insert time.
|
||||
|
||||
See also: https://en.wikipedia.org/wiki/Heap_(data_structure)
|
||||
|
||||
You can use it for things like:
|
||||
|
||||
- Help with scientific computing
|
||||
- Quickly sorting
|
||||
- Priority queues
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
|
||||
|
||||
1. Add heap to your list of dependencies in `mix.exs`:
|
||||
|
||||
def deps do
|
||||
[{:heap, "~> 0.0.1"}]
|
||||
end
|
||||
|
||||
## Examples
|
||||
|
||||
Create a min heap and use it to find the smallest element in a collection:
|
||||
|
||||
```elixir
|
||||
1..500 |> Enum.shuffle |> Enum.into(Heap.min) |> Heap.root
|
||||
# => 1
|
||||
```
|
||||
|
||||
Likewise, for max heaps:
|
||||
|
||||
```elixir
|
||||
1..500 |> Enum.shuffle |> Enum.into(Heap.max) |> Heap.root
|
||||
# => 500
|
||||
```
|
||||
|
||||
A priority queue:
|
||||
|
||||
Tuples are compared by their elements in order, so you can push tuples
|
||||
of `{priority, term}` into a Heap for sorting by priority:
|
||||
|
||||
```elixir
|
||||
Heap.new
|
||||
|> Heap.push({4, :jam})
|
||||
|> Heap.push({1, :milk})
|
||||
|> Heap.push({2, :eggs})
|
||||
|> Heap.push({1, :bread})
|
||||
|> Heap.push({3, :butter})
|
||||
|> Heap.push({2, :coffee})
|
||||
|> Enum.map(fn {_, what} -> what end)
|
||||
# => [:bread, :milk, :coffee, :eggs, :butter, :jam]
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
Full API documentation is available on (hexdocs.pm)[https://hexdocs.pm/heap]
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it ( https://github.com/Huia/Huia/fork )
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create a new Pull Request
|
30
config/config.exs
Normal file
30
config/config.exs
Normal file
|
@ -0,0 +1,30 @@
|
|||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for
|
||||
# 3rd-party users, it should be done in your "mix.exs" file.
|
||||
|
||||
# You can configure for your application as:
|
||||
#
|
||||
# config :heap, key: :value
|
||||
#
|
||||
# And access this configuration in your application as:
|
||||
#
|
||||
# Application.get_env(:heap, :key)
|
||||
#
|
||||
# Or configure a 3rd-party app:
|
||||
#
|
||||
# config :logger, level: :info
|
||||
#
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
# import_config "#{Mix.env}.exs"
|
112
lib/heap.ex
Normal file
112
lib/heap.ex
Normal file
|
@ -0,0 +1,112 @@
|
|||
defmodule Heap do
|
||||
defstruct h: nil, n: 0, d: nil
|
||||
@moduledoc """
|
||||
A heap is a special tree data structure. Good for sorting and other magic.
|
||||
|
||||
See also: [Heap (data structure) on Wikipedia](https://en.wikipedia.org/wiki/Heap_(data_structure)).
|
||||
"""
|
||||
|
||||
@type t :: %Heap{}
|
||||
|
||||
@doc """
|
||||
Create an empty min heap.
|
||||
|
||||
A min heap is a heap tree which always has the smallest value at the root.
|
||||
"""
|
||||
@spec min() :: Heap.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.
|
||||
"""
|
||||
@spec max() :: Heap.t
|
||||
def max(), do: Heap.new(:<)
|
||||
|
||||
@doc """
|
||||
Create an empty heap.
|
||||
"""
|
||||
@spec new() :: Heap.t
|
||||
@spec new(:> | :<) :: Heap.t
|
||||
def new(direction \\ :>), do: %Heap{d: direction}
|
||||
|
||||
@doc """
|
||||
Test if the heap is empty.
|
||||
"""
|
||||
@spec empty?(Heap.t) :: boolean()
|
||||
def empty?(%Heap{h: nil, n: 0}), do: true
|
||||
def empty?(%Heap{}), do: false
|
||||
|
||||
@doc """
|
||||
Test it the heap contains the element <elem>
|
||||
"""
|
||||
@spec member?(Heap.t, any()) :: boolean()
|
||||
def member?(%Heap{}=heap, value) do
|
||||
root = Heap.root heap
|
||||
heap = Heap.pop heap
|
||||
has_member? heap, root, value
|
||||
end
|
||||
|
||||
@doc """
|
||||
Push a new element into the heap.
|
||||
"""
|
||||
@spec push(Heap.t, any()) :: Heap.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.
|
||||
"""
|
||||
@spec pop(Heap.t) :: Heap.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.
|
||||
"""
|
||||
@spec root(Heap.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.
|
||||
"""
|
||||
@spec size(Heap.t) :: non_neg_integer()
|
||||
def size(%Heap{n: n}), do: n
|
||||
|
||||
@doc """
|
||||
Quickly sort an enumerable with a heap.
|
||||
"""
|
||||
@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
|
||||
|
||||
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 pair([], _), do: nil
|
||||
defp pair([q], _), do: q
|
||||
defp pair([q0, q1 | q], d) do
|
||||
q2 = meld(q0, q1, d)
|
||||
meld(q2, pair(q, d), d)
|
||||
end
|
||||
|
||||
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
|
||||
has_member? heap, previous, compare
|
||||
end
|
||||
end
|
11
lib/heap/collectable.ex
Normal file
11
lib/heap/collectable.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defimpl Collectable, for: Heap do
|
||||
|
||||
def into heap do
|
||||
{heap, fn
|
||||
(h, {:cont, v}) -> Heap.push h, v
|
||||
(h, :done) -> h
|
||||
(_, :halt) -> :ok
|
||||
end}
|
||||
end
|
||||
|
||||
end
|
23
lib/heap/enumerable.ex
Normal file
23
lib/heap/enumerable.ex
Normal file
|
@ -0,0 +1,23 @@
|
|||
defimpl Enumerable, for: Heap do
|
||||
|
||||
def count heap do
|
||||
{:ok, Heap.size heap}
|
||||
end
|
||||
|
||||
def member? heap, v do
|
||||
{:ok, Heap.member?(heap, v)}
|
||||
end
|
||||
|
||||
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
|
||||
nil ->
|
||||
{:done, acc}
|
||||
root ->
|
||||
heap = Heap.pop heap
|
||||
reduce(heap, fun.(root, acc), fun)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
11
lib/heap/inspect.ex
Normal file
11
lib/heap/inspect.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
defimpl Inspect, for: Heap do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect heap, opts do
|
||||
concat [
|
||||
"#Heap<",
|
||||
to_doc(Enum.to_list(heap), opts),
|
||||
">"
|
||||
]
|
||||
end
|
||||
end
|
43
mix.exs
Normal file
43
mix.exs
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule Heap.Mixfile do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[app: :heap,
|
||||
version: "0.0.1",
|
||||
maintainers: [ "James Harton <james@messagerocket.co>" ],
|
||||
licenses: [ "MIT" ],
|
||||
links: %{
|
||||
"GitHub" => "https://github.com/jamesotron/heap",
|
||||
"Docs" => "https://hexdocs.pm/heap"
|
||||
},
|
||||
description: "Heap data structure and tools",
|
||||
elixir: "~> 1.2",
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
preferred_cli_env: [espec: :test],
|
||||
deps: deps]
|
||||
end
|
||||
|
||||
# Configuration for the OTP application
|
||||
#
|
||||
# Type "mix help compile.app" for more information
|
||||
def application do
|
||||
[applications: [:logger]]
|
||||
end
|
||||
|
||||
# Dependencies can be Hex packages:
|
||||
#
|
||||
# {:mydep, "~> 0.3.0"}
|
||||
#
|
||||
# Or git/path repositories:
|
||||
#
|
||||
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
|
||||
#
|
||||
# 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}
|
||||
]
|
||||
end
|
||||
end
|
3
mix.lock
Normal file
3
mix.lock
Normal file
|
@ -0,0 +1,3 @@
|
|||
%{"espec": {:hex, :espec, "0.8.15"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.11.4"},
|
||||
"meck": {:hex, :meck, "0.8.4"}}
|
12
spec/heap/collectable_spec.exs
Normal file
12
spec/heap/collectable_spec.exs
Normal file
|
@ -0,0 +1,12 @@
|
|||
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
|
34
spec/heap/enumerable_spec.exs
Normal file
34
spec/heap/enumerable_spec.exs
Normal file
|
@ -0,0 +1,34 @@
|
|||
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
|
10
spec/heap/inspect_spec.exs
Normal file
10
spec/heap/inspect_spec.exs
Normal file
|
@ -0,0 +1,10 @@
|
|||
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
|
123
spec/heap_spec.exs
Normal file
123
spec/heap_spec.exs
Normal file
|
@ -0,0 +1,123 @@
|
|||
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
|
11
spec/spec_helper.exs
Normal file
11
spec/spec_helper.exs
Normal file
|
@ -0,0 +1,11 @@
|
|||
ESpec.start
|
||||
|
||||
ESpec.configure fn(config) ->
|
||||
config.before fn ->
|
||||
{:shared, hello: :world}
|
||||
end
|
||||
|
||||
config.finally fn(_shared) ->
|
||||
:ok
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue