Current section
Files
Jump to
Current section
Files
lib/skewheap.ex
defmodule Skewheap do
@moduledoc """
Skewheap - a mergable priority queue
Skewheaps are fun, weird, priority queues that self-balance over time.
Their structural depth is not guaranteed and individual operations may vary
in performance. That said, its _amortized_ performance is roughly O(log n)
([source](https://en.wikipedia.org/wiki/Skew_heap)).
Skewheaps' most interesting characteristic is that they can be _very_ quickly
merged together non-destructively, creating a new, balanced heap containing
all elements of the source heaps.
## Examples
iex> 1..10 |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.drain()
{%Skewheap{root: nil, size: 0}, [1,2,3,4,5,6,7,8,9,10]}
iex> a = 1..3 |> Enum.shuffle() |> Enum.into(Skewheap.new())
iex> b = 4..6 |> Enum.shuffle() |> Enum.into(Skewheap.new())
iex> Skewheap.merge(a, b) |> Skewheap.drain()
{%Skewheap{root: nil, size: 0}, [1,2,3,4,5,6]}
"""
defmodule Node do
@moduledoc "An individual node in a Skewheap. This should probably not be used directly."
defstruct payload: nil, left: nil, right: nil
@type skewnode :: nil | %Node{
payload: any(),
left: node,
right: node,
}
@spec new(any(), node, node) :: skewnode
def new(p, l \\ nil, r \\ nil), do: %Node{payload: p, left: l, right: r}
@spec merge(skewnode, skewnode) :: skewnode
def merge(a, b) when is_nil(a) and is_nil(b), do: nil
def merge(a, b) when is_nil(a), do: b
def merge(a, b) when is_nil(b), do: a
def merge(a, b) when a.payload > b.payload, do: merge(b, a)
def merge(a, b), do: %Node{payload: a.payload, left: merge(b, a.right), right: a.left}
end
defstruct size: 0, root: nil
@type skewheap :: %Skewheap{
size: non_neg_integer(),
root: Node.skewnode,
}
@doc """
Returns a new Skewheap.
## Examples
iex> Skewheap.new()
%Skewheap{size: 0, root: nil}
"""
@spec new() :: skewheap
def new(), do: %Skewheap{}
@doc """
True when the Skewheap has no items in it.
## Examples
iex> Skewheap.new() |> Skewheap.empty?()
true
iex> 1..10 |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.empty?()
false
"""
defmacro empty?(skew) do
quote do
(unquote(skew)).size == 0
end
end
@doc """
Returns the number of items in the Skewheap.
## Examples
iex> Skewheap.new() |> Skewheap.size()
0
iex> 1..10 |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.size()
10
"""
@spec size(skewheap) :: non_neg_integer()
def size(skew), do: skew.size
@doc """
Returns the top element of the heap without removing it or nil if empty.
## Examples
iex> 1..10 |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.peek()
1
"""
@spec peek(skewheap) :: any()
def peek(skew) when empty?(skew), do: nil
def peek(skew), do: skew.root.payload
@doc """
Adds a new element to the heap.
## Examples
iex> s = Skewheap.new()
iex> s = Skewheap.put(s, 42)
iex> Skewheap.put(s, "fnord")
%Skewheap{size: 2, root: %Skewheap.Node{left: %Skewheap.Node{left: nil, payload: "fnord", right: nil}, payload: 42, right: nil}}
"""
@spec put(skewheap, any()) :: skewheap
def put(skew, payload) when empty?(skew), do: %Skewheap{size: 1, root: Node.new(payload)}
def put(skew, payload), do: %Skewheap{size: skew.size + 1, root: Node.merge(skew.root, Node.new(payload))}
@doc """
Retrieves the top element from the heap or nil if empty.
## Examples
iex> [1,2,3] |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.take()
{%Skewheap{root: %Skewheap.Node{left: %Skewheap.Node{left: nil, payload: 3, right: nil}, payload: 2, right: nil}, size: 2}, 1}
"""
@spec take(skewheap) :: {skewheap, any()}
def take(skew) when empty?(skew), do: {skew, nil}
def take(skew) do
%Node{:payload => payload} = skew.root
{%Skewheap{size: skew.size - 1, root: Node.merge(skew.root.left, skew.root.right)}, payload}
end
@doc """
Removes all elements from the heap and returns them as a list.
## Examples
iex> 1..10 |> Enum.shuffle() |> Enum.into(Skewheap.new()) |> Skewheap.drain()
{%Skewheap{root: nil, size: 0}, [1,2,3,4,5,6,7,8,9,10]}
"""
@spec drain(skewheap) :: {skewheap, [any()]}
def drain(skew) when empty?(skew), do: {skew, []}
def drain(skew) do
{skew, payload} = take(skew)
{skew, rest} = drain(skew)
{skew, [payload | rest]}
end
@doc """
Merges two skew heaps into a new heap.
## Examples
iex> a = 1..3 |> Enum.shuffle() |> Enum.into(Skewheap.new())
iex> b = 4..6 |> Enum.shuffle() |> Enum.into(Skewheap.new())
iex> Skewheap.merge(a, b) |> Skewheap.drain()
{%Skewheap{root: nil, size: 0}, [1,2,3,4,5,6]}
"""
@spec merge(skewheap, skewheap) :: skewheap
def merge(a, b) do
%Skewheap{
size: a.size + b.size,
root: Node.merge(a.root, b.root),
}
end
end