View Source Qu (qu v0.1.0)
Qu
is an Elixir library that provides a single interface to four different types of
queues: FIFO, LIFO, circular, and priority.
Usage
All of the queue types support the same operations: Qu.put/2
to add an item,
Qu.pop/1
to remove the head, Qu.peek/1
to view the head without removal,
Qu.pull/2
to remove multiple items, and Qu.size/1
to get the number of
items in the queue. These operations are demonstrated below. For more details
about the queue operations, see the documentation for each function.
To create and fill a FIFO queue with a maximum size of 3, use Qu.new/2
and
then apply Qu.put/2
in a reduction:
fifo = Qu.new(:fifo, max_size: 3)
#=> #FIFO<read: [], write: [], size: 0, max_size: 3>
fifo =
Enum.reduce(["a", "b", "c"], fifo, fn item, q ->
{:ok, q} = Qu.put(q, item)
q
end)
#=> #FIFO<read: [], write: ["c", "b", "a"], size: 3, max_size: 3>
A priority queue expects items of the form {priority_key, value}
. Here is
how to create and populate a priority queue where items are popped in
ascending order of priority:
prio = Qu.new(:priority, priority_order: :asc)
Enum.reduce([{1, "a"}, {2, "b"}, {3, "c"}], prio, fn item, q ->
{:ok, q} = Qu.put(q, item)
q
end)
#=> #Priority<heap: #PairingHeap<root: {1, "a"}, size: 3, mode: :min>, size: 3, max_size: nil>
For FIFO, LIFO, and priority queues, applying Qu.put/2
to a full queue returns
:error
:
Qu.new(:fifo, max_size: 0) |> Qu.put(fifo, "a")
#=> :error
An error is never returned when adding items to a circular queue, since the
oldest item is discarded when inserting into a full circular queue. To preserve
the common interface, Qu.put/2
still returns {:ok, updated_queue}
for a
circular queue.
The head of the queue can be removed and returned with Qu.pop/1
:
{:ok, fifo} = Qu.new(:fifo) |> Qu.put("a")
Qu.pop(fifo)
#=> {:ok, "a", #FIFO<read: [], write: [], size: 0, max_size: nil>}
For all queue types, Qu.pop/1
returns :error
if the queue is empty:
Qu.new(:fifo, max_size: 0) |> Qu.pop()
#=> :error
The head can be viewed without modifying the queue using Qu.peek/1
:
{:ok, fifo} = Qu.new(:fifo) |> Qu.put("a")
Qu.peek(fifo)
#=> {:ok, "a"}
Similar to Qu.pop/1
, Qu.peek/1
will return :error
if the queue is empty.
Use Qu.pull/2
to remove and return multiple items:
fifo = Qu.new(:fifo, max_size: 3)
["a", "b", "c"]
|> Enum.reduce(fifo, fn item, q ->
{:ok, q} = Qu.put(q, item)
q
end)
|> Qu.pull(3)
#=> {["a", "b", "c"], #FIFO<read: [], write: [], size: 0, max_size: 3>}
Note that Qu.pull/2
does not return :error
if the queue is empty or if the
number of elements pulled is larger than the queue size.
For all of the queue types, Qu.size/1
returns the current size of the queue:
{:ok, fifo} = Qu.new(:fifo) |> Qu.put("a")
Qu.size(fifo)
#=> 1
All of the queue types also implement the Enumerable
and Collectable
protocols, so that Enum.reduce/3
, Enum.to_list/1
, and Enum.into/2
may
be used.
Supported queue types
Listed below are the supported queue types, as well as notes about their implementations and usage guidelines.
FIFO
A first-in, first-out queue, in which the oldest item in the queue will be the next item removed. If the queue has a finite maximum size and is full, the next insert will fail.
The implementation follows the
Erlang queue and uses separate read and
write lists to achieve O(1)
amortized time for insert and delete operations.
LIFO
A last-in, first-out queue, in which the item most recently added to the queue will be the next item removed. This is also known as a stack. If the queue has a finite maximum size and is full, the next insert will fail.
The implementation uses a single Elixir list and has O(1)
time for both
insert and delete operations.
Circular
A circular queue is similar to a FIFO queue in that the oldest item in the queue will be the next item removed. However, when adding an item to a full circular queue, the oldest item will be discarded to preserve the fixed size. An insert in a full circular queue will always succeed.
The implementation borrows from the FIFO implementation, but has a modified
pop/1
function to achieve the properties described above.
Priority
In a priority queue, items are added as key-value pairs, where the key is a sortable priority. Items are removed in priority order, either descending or ascending, depending on the configuration.
The implementation uses a heap, specifically a pairing heap
(see the PairingHeap
Elixir library), which has O(1)
insert time and O(log n)
amortized deletion
time.
Summary
Types
A queue item, either a single value or, in the case of a priority queue, a tuple of a priority key and a value.
Maximum queue size. A value of nil
indicates that the queue has no maximum
size.
Type of queue.
Current size of the queue.
Functions
Create a new queue of the given type and maximum size.
Get the item at the head of the queue.
Pop the item at the head of the queue.
Return the first n
items in the queue and the final state of the heap.
Insert an item into the queue.
Return the size of the queue.
Types
A queue item, either a single value or, in the case of a priority queue, a tuple of a priority key and a value.
@type max_size() :: non_neg_integer() | nil
Maximum queue size. A value of nil
indicates that the queue has no maximum
size.
@type queue_type() :: :fifo | :lifo | :circular | :priority
Type of queue.
@type size() :: non_neg_integer()
Current size of the queue.
Functions
@spec new(type :: queue_type(), opts :: keyword()) :: Qu.Queue.t()
Create a new queue of the given type and maximum size.
Options
:max_size
- Maximum size of the queue. A value ofnil
indicates that the queue has no maximum size. When the queue type is:circular
, the maximum must be a positive integer. The default value isnil
.:priority_order
- Key order in which items are removed when the queue type is:priority
. When a tuple like{:desc, DateTime}
is given, the key is assumed to be aDateTime
, andDateTime.compare/2
is used compare keys. The default value is:asc
.
Examples
iex> Qu.new(:fifo, max_size: 10)
#FIFO<read: [], write: [], size: 0, max_size: 10>
iex> Qu.new(:priority, priority_order: {:asc, DateTime})
#Priority<heap: #PairingHeap<root: :empty, size: 0, mode: {:min, DateTime}>, size: 0, max_size: nil>
@spec peek(Qu.Queue.t()) :: {:ok, item()} | :error
Get the item at the head of the queue.
If there is at least one item in the queue, this returns
{:ok, item}
. If the queue is empty, :error
is returned.
Examples
iex> {:ok, q} = Qu.new(:fifo, max_size: 1) |> Qu.put("a")
iex> {:ok, "a"} = Qu.peek(q)
@spec pop(Qu.Queue.t()) :: {:ok, item(), Qu.Queue.t()} | :error
Pop the item at the head of the queue.
If there is at least one item in the queue, this returns
{:ok, item, updated_queue}
. If the queue is empty, :error
is returned.
Examples
iex> {:ok, q} = Qu.new(:fifo) |> Qu.put("a")
iex> {:ok, "a", q} = Qu.pop(q)
iex> q
#FIFO<read: [], write: [], size: 0, max_size: nil>
iex> Qu.pop(q)
:error
@spec pull(Qu.Queue.t(), non_neg_integer()) :: {[item()], Qu.Queue.t()}
Return the first n
items in the queue and the final state of the heap.
If the queue size is less than n
, all items are returned along with
an empty heap.
Note that pull
is often more convenient than pop
, because of it can pop
multiple items at once, and because it never returns :error
.
Examples
iex> {:ok, q} = Qu.new(:fifo, max_size: 1) |> Qu.put("a")
iex> {["a"], _} = Qu.pull(q, 1)
@spec put(Qu.Queue.t(), item()) :: {:ok, Qu.Queue.t()} | :error
Insert an item into the queue.
If the queue size is less than the maximum size, this returns
{:ok, updated_queue}
. With the exception of a cirular queue, if the queue
size equals the maximum size, :error
is returned. For a circular queue,
Qu.put/2
never returns :error
.
Examples
iex> {:ok, q} = Qu.new(:fifo, max_size: 1) |> Qu.put("a")
iex> q
#FIFO<read: [], write: ["a"], size: 1, max_size: 1>
iex> Qu.put(q, "b")
:error
iex> {:ok, q} = Qu.new(:priority, priority_order: :asc) |> Qu.put({1, "a"})
iex> q
#Priority<heap: #PairingHeap<root: {1, "a"}, size: 1, mode: :min>, size: 1, max_size: nil>
@spec size(Qu.Queue.t()) :: size()
Return the size of the queue.
Examples
iex> q = Qu.new(:fifo, max_size: 1)
iex> Qu.size(q)
0
iex> {:ok, q} = Qu.put(q, "a")
iex> Qu.size(q)
1