defmodule PSQ do @moduledoc """ 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](https://hackage.haskell.org/package/PSQueue-1.1/docs/Data-PSQueue.html) package and the associated paper. PSQs can be created from lists in O(n log n) time. Once created, the minimum element (`min`) and size (`Enum.count`) can be accessed in O(1) time; most other basic operations (including `get`, `pop`, and `push`, and `delete`) 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`, use keys to find the entry; all entries in a PSQ are unique by key. Ordered operations, such as `pop` and `Enum.to_list`, 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} """ defstruct tree: :void, key_mapper: nil, priority_mapper: nil alias PSQ.Winner alias PSQ.Loser alias PSQ.Entry @type key :: any @type value :: any @type priority :: any @type key_mapper :: (value -> key) @type priority_mapper :: (value -> priority) @type t :: %__MODULE__{tree: Winner.t, key_mapper: key_mapper, priority_mapper: priority_mapper} @doc """ 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. """ @spec new(priority_mapper, key_mapper) :: t def new(priority_mapper \\ &(&1), key_mapper \\ &(&1)) do %PSQ{key_mapper: key_mapper, priority_mapper: priority_mapper} end @doc """ Returns a new PSQ from `list`. `priority_mapper` and `key_mapper` behave the same way as for `new`. ## Examples iex> [2, 5, 4, 1, 3] |> PSQ.from_list |> Enum.to_list [1, 2, 3, 4, 5] """ @spec from_list(list(value), priority_mapper, key_mapper) :: t def from_list(list, priority_mapper \\ &(&1), key_mapper \\ &(&1)) do q = new(priority_mapper, key_mapper) list |> Enum.into(q) end @doc """ 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] """ @spec put(t, value) :: t def put(q = %PSQ{priority_mapper: priority_mapper, key_mapper: key_mapper}, val) do put(q, key_mapper.(val), val, priority_mapper.(val)) end @doc """ 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] """ @spec put(t, key, value, priority) :: t def put(q = %PSQ{tree: tree}, key, val, priority) do entry = Entry.new(val, priority, key) %PSQ{q | tree: do_put(tree, entry)} end @spec do_put(Winner.t, Entry.t) :: Winner.t defp do_put(:void, entry), do: Winner.new(entry, :start, Entry.key(entry)) defp do_put(winner = {winner_entry, :start, max_key}, entry) do winner_key = Entry.key(winner_entry) entry_key = Entry.key(entry) cond do winner_key < entry_key -> play(winner, Winner.new(entry, :start, entry_key)) winner_key == entry_key -> Winner.new(entry, :start, max_key) winner_key > entry_key -> play(Winner.new(entry, :start, entry_key), winner) end end defp do_put(winner, entry) do {t1, t2} = unplay(winner) if Entry.key(entry) <= Winner.max_key(t1) do play(do_put(t1, entry), t2) else play(t1, do_put(t2, entry)) end end @doc """ 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 """ @spec pop(t) :: {value, t} def pop(q = %PSQ{tree: :void}) do {nil, q} end def pop(q = %PSQ{tree: {entry, loser, max_key}}) do new_winner = second_best(loser, max_key) {Entry.value(entry), %PSQ{q | tree: new_winner}} end @doc """ 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 """ @spec min(t) :: value | no_return def min(%PSQ{tree: :void}) do raise Enum.EmptyError end def min(%PSQ{tree: tree}) do tree |> Winner.entry |> Entry.value end @doc """ 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 """ @spec get(t, key) :: value def get(q, key) do case fetch(q, key) do {:ok, val} -> val :error -> nil end end @doc """ 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 """ @spec fetch(t, key) :: {:ok, value} | :error def fetch(%PSQ{tree: tree}, key) do do_fetch(tree, key) end @doc """ 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 """ @spec fetch!(t, key) :: value | no_return def fetch!(q, key) do case fetch(q, key) do {:ok, val} -> val :error -> raise KeyError, key: key, term: q end end @spec do_fetch(Winner.t, key) :: {:ok, value} | :error defp do_fetch(:void, _), do: :error defp do_fetch({entry, :start, _}, key) do case Entry.key(entry) do ^key -> {:ok, Entry.value(entry)} _ -> :error end end defp do_fetch(winner, key) do {t1, t2} = unplay(winner) if key <= Winner.max_key(t1) do do_fetch(t1, key) else do_fetch(t2, key) end end @doc """ 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] """ @spec delete(t, key) :: t def delete(q = %PSQ{tree: tree}, key) do new_tree = do_delete(tree, key) %PSQ{q | tree: new_tree} end @spec do_delete(Winner.t, key) :: Winner.t defp do_delete(:void, _), do: :void defp do_delete(winner = {entry, :start, _}, key) do case Entry.key(entry) do ^key -> :void _ -> winner end end defp do_delete(winner, key) do {t1, t2} = unplay(winner) if key <= Winner.max_key(t1) do play(do_delete(t1, key), t2) else play(t1, do_delete(t2, key)) end end @doc """ 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] """ @spec at_most(t, priority) :: list(value) def at_most(%PSQ{tree: tree}, priority) do do_at_most(tree, priority) end @spec do_at_most(Winner.t, priority) :: list(value) defp do_at_most(:void, _), do: [] defp do_at_most({{_, _, priority}, _, _}, max_priority) when priority > max_priority do [] end defp do_at_most({entry, :start, _}, _) do [Entry.value(entry)] end defp do_at_most(winner, max_priority) do {t1, t2} = unplay(winner) do_at_most(t1, max_priority) ++ do_at_most(t2, max_priority) end # "Tournament" functions @spec play(Winner.t, Winner.t) :: Winner.t defp play(:void, t), do: t defp play(t, :void), do: t defp play({e1, l1, k1}, {e2, l2, k2}) when k1 < k2 do p1 = Entry.priority(e1) p2 = Entry.priority(e2) if p1 <= p2 do loser = Loser.new(e2, l1, k1, l2) |> balance Winner.new(e1, loser, k2) else loser = Loser.new(e1, l1, k1, l2) |> balance Winner.new(e2, loser, k2) end end @spec unplay(Winner.t) :: {Winner.t, Winner.t} defp unplay({winner_entry, loser = {loser_entry, left, split_key, right, _}, max_key}) do {left_entry, right_entry} = case Loser.origin(loser) do :right -> {winner_entry, loser_entry} :left -> {loser_entry, winner_entry} end { Winner.new(left_entry, left, split_key), Winner.new(right_entry, right, max_key), } end @spec second_best(Loser.t, key) :: Winner.t defp second_best(:start, _), do: :void defp second_best({entry, left, split_key, right, _}, max_key) do key = Entry.key(entry) if key <= split_key do play( Winner.new(entry, left, split_key), second_best(right, max_key) ) else play( second_best(left, split_key), Winner.new(entry, right, max_key) ) end end # Balancing functions @balance_factor 4.0 @spec balance(Loser.t) :: Loser.t defp balance(:start), do: :start defp balance(loser = {_, left, _, right, _}) do l = Loser.size(left) r = Loser.size(right) cond do l + r < 2 -> loser r > (@balance_factor * l) -> balance_left(loser) l > (@balance_factor * r) -> balance_right(loser) true -> loser end end @spec balance_left(Loser.t) :: Loser.t defp balance_left(loser) do right = Loser.right(loser) rl = Loser.left(right) rr = Loser.right(right) if Loser.size(rl) < Loser.size(rr) do single_left(loser) else double_left(loser) end end @spec balance_right(Loser.t) :: Loser.t defp balance_right(loser) do left = Loser.left(loser) ll = Loser.left(left) lr = Loser.right(left) if Loser.size(lr) < Loser.size(ll) do single_right(loser) else double_right(loser) end end @spec single_left(Loser.t) :: Loser.t defp single_left(loser) do {e1, t1, k1, right, _} = loser {e2, t2, k2, t3, _} = right if Entry.key(e2) <= k2 && Entry.priority(e1) <= Entry.priority(e2) do new_left = Loser.new(e2, t1, k1, t2) Loser.new(e1, new_left, k2, t3) else new_left = Loser.new(e1, t1, k1, t2) Loser.new(e2, new_left, k2, t3) end end @spec single_right(Loser.t) :: Loser.t defp single_right(loser) do {e1, left, k2, t3, _} = loser {e2, t1, k1, t2, _} = left if Entry.key(e2) > k1 && Entry.priority(e1) <= Entry.priority(e2) do new_right = Loser.new(e2, t2, k2, t3) Loser.new(e1, t1, k1, new_right) else new_right = Loser.new(e1, t2, k2, t3) Loser.new(e2, t1, k1, new_right) end end @spec double_left(Loser.t) :: Loser.t defp double_left({entry, left, split_key, right, _}) do single_left(Loser.new(entry, left, split_key, single_right(right))) end @spec double_right(Loser.t) :: Loser.t defp double_right({entry, left, split_key, right, _}) do single_right(Loser.new(entry, single_left(left), split_key, right)) end end defimpl Enumerable, for: PSQ do def count(%PSQ{tree: :void}), do: {:ok, 0} def count(%PSQ{tree: winner}) do loser = PSQ.Winner.loser(winner) {:ok, PSQ.Loser.size(loser) + 1} end def member?(q, element) do case PSQ.fetch(q, element) do {:ok, _} -> {:ok, true} :error -> {:ok, false} end end def reduce(_, {:halt, acc}, _fun), do: {:halted, acc} def reduce(q, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(q, &1, fun)} def reduce(%PSQ{tree: :void}, {:cont, acc}, _fun), do: {:done, acc} def reduce(q, {:cont, acc}, fun) do {x, rest} = PSQ.pop(q) reduce(rest, fun.(x, acc), fun) end end defimpl Collectable, for: PSQ do def into(original) do {original, fn q, {:cont, x} -> PSQ.put(q, x) q, :done -> q _, :halt -> :ok end} end end defimpl Inspect, for: PSQ do import Inspect.Algebra def inspect(q, opts) do case q.tree do :void -> "#PSQ" _ -> concat ["#PSQ"] end end end