Packages

An efficient implementation of double ended fifo queues

Current section

Files

Jump to
quex lib quex.ex
Raw

lib/quex.ex

defmodule Quex do
@moduledoc File.read!("README.md")
defstruct front: [], rear: []
@type t :: %Quex{front: list, rear: list}
@doc "Creates a new empty queue"
@spec new :: t
def new do
%Quex{}
end
@doc """
Creates a new queue from a list
The first element in the list will be the front item of the queue.
"""
@spec new(list) :: t
def new(items) do
f2r(items)
end
@doc "Puts the given value at the end the queue"
@spec put(t, term) :: t
def put(%Quex{front: [], rear: rear = [_]}, item) do
%Quex{front: rear, rear: [item]}
end
def put(%Quex{rear: rear} = queue, item) do
%Quex{queue | rear: [item | rear]}
end
@doc """
Puts the given value at the front of the queue
This means that it will be the first item in the queue to pop, peek, or drop.
"""
@spec put_front(t, term) :: t
def put_front(%Quex{front: front = [_], rear: []}, item) do
%Quex{front: [item], rear: front}
end
def put_front(%Quex{front: front} = queue, item) do
%Quex{queue | front: [item | front]}
end
@doc """
Pop the first value from the front of the queue
Returns the value as well the rest of the queue or `:empty` if the queue has
no items.
"""
@spec pop(t) :: {term, t} | :empty
def pop(%Quex{front: [], rear: []}) do
:empty
end
def pop(%Quex{front: [], rear: [item]}) do
{item, %Quex{front: [], rear: []}}
end
def pop(%Quex{front: [], rear: [last | rest]}) do
[item | front] = :lists.reverse(rest, [])
{item, %Quex{front: front, rear: [last]}}
end
def pop(%Quex{front: [item], rear: rear}) do
{item, r2f(rear)}
end
def pop(%Quex{front: [item | rest]} = queue) do
{item, %Quex{queue | front: rest}}
end
@doc """
Pop the last value from the rear of the queue
Returns the value as well the rest of the queue or `:empty` if the queue has
no items.
"""
@spec pop_rear(t) :: {term, t} | :empty
def pop_rear(%Quex{front: [], rear: []}) do
:empty
end
def pop_rear(%Quex{front: [item], rear: []}) do
{item, %Quex{front: [], rear: []}}
end
def pop_rear(%Quex{front: [first | rest], rear: []}) do
[item | rear] = :lists.reverse(rest, [])
{item, %Quex{front: [first], rear: rear}}
end
def pop_rear(%Quex{front: front, rear: [item]}) do
{item, f2r(front)}
end
def pop_rear(%Quex{rear: [item | rest]} = queue) do
{item, %Quex{queue | rear: rest}}
end
@doc """
Remove the first value from the front of the queue
Returns the rest of the queue or `:empty` if the queue has no items.
"""
@spec drop(t) :: t | :empty
def drop(%Quex{front: [], rear: []}) do
:empty
end
def drop(%Quex{front: [], rear: [_item]}) do
%Quex{front: [], rear: []}
end
def drop(%Quex{front: [], rear: [last | rest]}) do
[_item | front] = :lists.reverse(rest, [])
%Quex{front: front, rear: [last]}
end
def drop(%Quex{front: [_item], rear: rear}) do
r2f(rear)
end
def drop(%Quex{front: [_item | rest]} = queue) do
%Quex{queue | front: rest}
end
@doc """
Remove the last value from the rear of the queue
Returns the rest of the queue or `:empty` if the queue has no items.
"""
@spec drop_rear(t) :: t | :empty
def drop_rear(%Quex{front: [], rear: []}) do
:empty
end
def drop_rear(%Quex{front: [_item], rear: []}) do
%Quex{front: [], rear: []}
end
def drop_rear(%Quex{front: [first | rest], rear: []}) do
[_item | rear] = :lists.reverse(rest, [])
%Quex{front: [first], rear: rear}
end
def drop_rear(%Quex{front: front, rear: [_item]}) do
f2r(front)
end
def drop_rear(%Quex{rear: [_item | rest]} = queue) do
%Quex{queue | rear: rest}
end
@doc """
Get the first value from the front of the queue without removing it
Returns the `{:ok, value}` or `:empty` if the queue has no items.
"""
@spec peek(t) :: {:ok, term} | :empty
def peek(%Quex{front: [], rear: []}) do
:empty
end
def peek(%Quex{front: [item | _]}) do
{:ok, item}
end
def peek(%Quex{front: [], rear: [item]}) do
{:ok, item}
end
@doc """
Get the last value from the rear of the queue without removing it
Returns the `{:ok, value}` or `:empty` if the queue has no items.
"""
@spec peek_rear(t) :: {:ok, term} | :empty
def peek_rear(%Quex{front: [], rear: []}) do
:empty
end
def peek_rear(%Quex{rear: [item | _]}) do
{:ok, item}
end
def peek_rear(%Quex{front: [item], rear: []}) do
{:ok, item}
end
@doc """
Join two queues
It effectively appends the second queue to the first queue.
"""
@spec join(t, t) :: t
def join(%Quex{} = q, %Quex{front: [], rear: []}) do
q
end
def join(%Quex{front: [], rear: []}, %Quex{} = q) do
q
end
def join(%Quex{front: f1, rear: r1}, %Quex{front: f2, rear: r2}) do
%Quex{front: f1 ++ :lists.reverse(r1, f2), rear: r2}
end
@doc """
Converts a queue to a list
The front item of the queue will be the first element in the list.
"""
@spec to_list(t) :: list
def to_list(%Quex{front: front, rear: rear}) do
front ++ :lists.reverse(rear, [])
end
@doc "Converts a queue to Erlang's queue data type"
@spec to_erl(t) :: {list, list}
def to_erl(%Quex{front: front, rear: rear}) do
{rear, front}
end
@doc "Converts Erlang's queue data type to a queue"
@spec from_erl({list, list}) :: t
def from_erl({rear, front}) when is_list(rear) and is_list(front) do
%Quex{front: front, rear: rear}
end
@doc "Returns the number of items in the queue"
@spec size(t) :: non_neg_integer
def size(%Quex{front: front, rear: rear}) do
length(front) + length(rear)
end
@doc "Returns true if the given value exists in the queue"
@spec member?(t, term) :: boolean
def member?(%Quex{front: front, rear: rear}, item) do
:lists.member(item, rear) or :lists.member(item, front)
end
# Move half of elements from rear to front, if there are at least three
defp r2f([]), do: %Quex{}
defp r2f([_] = rear), do: %Quex{front: [], rear: rear}
defp r2f([x, y]), do: %Quex{front: [y], rear: [x]}
defp r2f(list) do
{rear, front} = :lists.split(div(length(list), 2) + 1, list)
%Quex{front: :lists.reverse(front, []), rear: rear}
end
# Move half of elements from front to rear, if there are enough
defp f2r([]), do: %Quex{}
defp f2r([_] = front), do: %Quex{front: [], rear: front}
defp f2r([x, y]), do: %Quex{front: [x], rear: [y]}
defp f2r(list) do
{front, rear} = :lists.split(div(length(list), 2) + 1, list)
%Quex{front: front, rear: :lists.reverse(rear, [])}
end
end
defimpl Enumerable, for: Quex do
def count(queue), do: {:ok, Quex.size(queue)}
def member?(queue, x), do: {:ok, Quex.member?(queue, x)}
def reduce(%Quex{front: front, rear: rear}, acc, fun) do
rear_acc = do_reduce(front, acc, fun)
case do_reduce(:lists.reverse(rear, []), rear_acc, fun) do
{:cont, acc} ->
{:done, acc}
{:halt, acc} ->
{:halted, acc}
suspended ->
suspended
end
end
defp do_reduce([h | t], {:cont, acc}, fun) do
do_reduce(t, fun.(h, acc), fun)
end
defp do_reduce([], {:cont, acc}, _fun) do
{:cont, acc}
end
defp do_reduce(_queue, {:halt, acc}, _fun) do
{:halt, acc}
end
defp do_reduce(queue, {:suspend, acc}, fun) do
{:suspended, acc, &do_reduce(queue, &1, fun)}
end
defp do_reduce(queue, {:suspended, acc, continuation}, fun) do
{:suspended, acc,
fn acc ->
rear_acc = continuation.(acc)
do_reduce(queue, rear_acc, fun)
end}
end
def slice(queue) do
{:ok, Quex.size(queue), &Quex.to_list/1}
end
end
defimpl Collectable, for: Quex do
def into(original) do
{original,
fn
queue, {:cont, item} ->
Quex.put(queue, item)
%Quex{front: [front], rear: [rear | rest]} = queue, :done ->
%{queue | front: [front | :lists.reverse(rest)], rear: [rear]}
_, :halt ->
:ok
end}
end
end
defimpl Inspect, for: Quex do
import Inspect.Algebra
def inspect(%Quex{} = queue, opts) do
concat(["Quex.new(", to_doc(Quex.to_list(queue), opts), ")"])
end
end