psq v0.1.0 PSQ
PSQ provides a purely-functional implementation of priority search queues. A
priority search queue is a data structure that efficiently supports both
associative operations (like those for Map
) and priority-queue operations
(akin to heaps in imperative languages). The implementation is based on the
Haskell
PSQueue
package and the associated paper.
PSQs can be created from lists in O(n log n) time. Once created, the minimum
element (min/1
and size (Enum.count/1
) can be accessed in O(1) time; most
other basic operations (including get/2
, pop/1
, and put/2
, and
delete/2
are in O(log n).
PSQs implement Enumerable
and Collectable
, so all your favorite functions
from Enum
and Stream
should work as expected.
Each entry in a PSQ has an associated priority and key. Map-like
operations, such as get/2
, use keys to find the entry; all entries in a PSQ
are unique by key. Ordered operations, such as pop/1
and Enum.to_list/1
,
use priority to determine order (with minimum first). Priorities need not be
unique by entry; entries with the same priority will be popped in unspecified
order.
Examples
There are two primary ways to determine a value’s priority and key in a
queue. The simplest is to start with an empty queue and input values with
priorities and keys directly, through put/4
:
iex> q = PSQ.new |> PSQ.put(:a, "foo", 2) |> PSQ.put(:b, "bar", 1)
iex> q |> PSQ.get(:a)
"foo"
iex> q |> PSQ.min
"bar"
Alternatively, you can specify mapper functions to determine key and priority for all entries in the queue. This is particularly useful for determining custom priorities. For example, here’s a simple method to use PSQs for max-queues:
iex> q = PSQ.new(&(-&1))
iex> q = [?a, ?b, ?c, ?d, ?e] |> Enum.into(q)
iex> q |> Enum.to_list
[?e, ?d, ?c, ?b, ?a]
Here’s a queue that orders strings by size, using downcased strings as keys:
iex> q = PSQ.new(&String.length/1, &String.downcase/1)
iex> q = ["How", "is", "your", "ocelot"] |> Enum.into(q)
iex> q |> Enum.to_list
["is", "How", "your", "ocelot"]
iex> q |> PSQ.get("how")
"How"
iex> q |> PSQ.get("How")
nil
Priority and key mappers are also useful if you’re inputting entries that are structs or maps and want to use particular fields as keys or priorities. For example:
iex> q = PSQ.new(&(&1[:priority]), &(&1[:key]))
iex> q = PSQ.put(q, %{priority: 5, key: 1})
iex> q = PSQ.put(q, %{priority: 2, key: 2})
iex> q = PSQ.put(q, %{priority: 1, key: 1})
iex> q |> PSQ.min
%{priority: 1, key: 1}
iex> q |> PSQ.get(1)
%{priority: 1, key: 1}
Summary
Functions
Returns a list of all values from q
where the value’s priority is less than
or equal to priority
Deletes the value associated with key
from q
Fetches the value for specified key
and returns in a tuple. Returns
:error
if the key does not exist
Fetches the value for specified key
Returns a new PSQ from list
Gets the value for specified key
. If the key does not exist, returns nil
Returns the value with the minimum priority from q
Returns a new empty PSQ
Returns and removes the value with the minimum priority from q
. The value
will be nil
if the queue is empty
Puts the given value
into the queue, using priority_mapper
and
key_mapper
to determine uniqueness/order (see new
)
Puts the given value
into the queue with specified key
and
priority
Types
key :: any
key_mapper :: (value -> key)
priority :: any
priority_mapper :: (value -> priority)
t :: %PSQ{key_mapper: key_mapper, priority_mapper: priority_mapper, tree: PSQ.Winner.t}
value :: any
Functions
Returns a list of all values from q
where the value’s priority is less than
or equal to priority
.
Examples
iex> PSQ.from_list([1, 3, 2, 5, 4]) |> PSQ.at_most(3)
[1, 2, 3]
Deletes the value associated with key
from q
.
If key
does not exist, returns q
unchanged.
Examples
iex> PSQ.from_list([3,1,2]) |> PSQ.delete(2) |> Enum.to_list
[1, 3]
iex> PSQ.from_list([3,1,2]) |> PSQ.delete(4) |> Enum.to_list
[1, 2, 3]
Fetches the value for specified key
and returns in a tuple. Returns
:error
if the key does not exist.
Examples
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.fetch(:a)
{:ok, 3}
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.fetch(:b)
:error
Fetches the value for specified key
.
If key
does not exist, a KeyError
is raised.
Examples
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.fetch!(:a)
3
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.fetch!(:b)
** (KeyError) key :b not found in: #PSQ<min:3 size:1>
Specs
from_list([value], priority_mapper, key_mapper) :: t
Returns a new PSQ from list
.
priority_mapper
and key_mapper
behave the same way as for new/2
.
Examples
iex> [2, 5, 4, 1, 3] |> PSQ.from_list |> Enum.to_list
[1, 2, 3, 4, 5]
Gets the value for specified key
. If the key does not exist, returns nil
.
Examples
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.get(:a)
3
iex> PSQ.new |> PSQ.put(:a, 3, 1) |> PSQ.get(:b)
nil
Returns the value with the minimum priority from q
.
Raises Enum.EmptyError
if the queue is empty.
Examples
iex> PSQ.from_list([-2, 3, -5]) |> PSQ.min
-5
iex> PSQ.from_list([-2, 3, -5], &(-&1)) |> PSQ.min
3
iex> PSQ.new |> PSQ.min
** (Enum.EmptyError) empty error
Specs
new(priority_mapper, key_mapper) :: t
Returns a new empty PSQ.
Optional params priority_mapper
and key_mapper
are functions to determine
keys and priorities from values. For example, to create a max-queue of numbers
instead of a min-queue, pass in &(-&1)
for priority_mapper
:
iex> PSQ.new(&(-&1)) |> PSQ.put(3) |> PSQ.put(5) |> PSQ.put(1) |> Enum.to_list
[5, 3, 1]
key_mapper
is useful if your values are structs where particular fields are
considered a unique key:
iex> q = PSQ.new(&(&1[:priority]), &(&1[:key]))
iex> q = q |> PSQ.put(%{key: 1, priority: 1})
iex> q = q |> PSQ.put(%{key: 1, priority: 3})
iex> q |> PSQ.get(1)
%{key: 1, priority: 3}
priority_mapper
and key_mapper
both default to the identity function.
Returns and removes the value with the minimum priority from q
. The value
will be nil
if the queue is empty.
Examples
iex> q = PSQ.from_list([3, 1])
iex> {min, q} = PSQ.pop(q)
iex> min
1
iex> {min, q} = PSQ.pop(q)
iex> min
3
iex> {min, q} = PSQ.pop(q)
iex> min
nil
iex> Enum.empty?(q)
true
Puts the given value
into the queue, using priority_mapper
and
key_mapper
to determine uniqueness/order (see new
).
If a value with the same key already exits in the queue, it will be replaced by the new value.
Examples
iex> q = PSQ.new(&(&1), &trunc/1)
iex> q = PSQ.put(q, 3.89)
iex> q = PSQ.put(q, 2.71)
iex> q = PSQ.put(q, 3.14)
iex> Enum.to_list(q)
[2.71, 3.14]
Puts the given value
into the queue with specified key
and
priority
.
When using this function (as opposed to put/2
, the queue’s
priority_mapper
and key_mapper
will be ignored. It is not recommended to
use both mappers and direct keys/priorities for the same queue.
Examples
iex> PSQ.new |> PSQ.put(:a, 1, 1) |> PSQ.put(:a, 2, 1) |> PSQ.get(:a)
2
iex> PSQ.new |> PSQ.put(:a, 1, 2) |> PSQ.put(:b, 2, 1) |> Enum.to_list
[2, 1]