View Source README

Hex.pm Version

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.