%--------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%--------------------------------------------------%
% Copyright (C) 2014-2019, 2021-2022, 2024-2025 The Mercury Team
% This file is distributed under the terms specified in COPYING.LIB.
%--------------------------------------------------%
%
% File: psqueue.m.
% Main author: Matthias Güdemann.
% Stability: high.
%
% This module implements priority search queues. A priority search queue,
% or psqueue for short, combines in a single ADT the functionality of both
% a map and a priority queue.
%
% Psqueues map from priorities to keys and back. This module provides functions
% and predicates to lookup the priority of a key, to insert and to remove
% priority-key pairs, to adjust the priority of a given key, and to retrieve
% the priority/key pair with the highest conceptual priority. However,
% since in many applications of psqueues, a low number represents high
% priority; for example, Dijkstra's shortest path algorithm wants to process
% the nearest nodes first. Therefore, given two priorities PrioA and PrioB,
% this module considers priority PrioA to have the higher conceptual priority
% if compare(CMP, PrioA, PrioB) returns CMP = (<). If priorities are numerical,
% which is common but is not required, then higher priorities are represented
% by lower numbers.
%
% The operations in this module are based on the algorithms described in
% Ralf Hinze: A simple implementation technique for priority search queues,
% Proceedings of the International Conference on Functional Programming 2001,
% pages 110-121. They use a weight-balanced tree to store priority/key pairs,
% to allow the following operation complexities:
%
% psqueue.insert insert new priority/key pair: O(log n)
% psqueue.lookup lookup the priority of a key: O(log n)
% psqueue.adjust adjust the priority of a key: O(log n)
% psqueue.peek: read highest priority pair: O(1)
% psqueue.remove_least: remove highest priority pair: O(log n)
% psqueue.remove remove pair with given key: O(log n)
%
%--------------------------------------------------%
%--------------------------------------------------%
:- module psqueue.
:- interface.
:- import_module assoc_list.
%--------------------------------------------------%
:- type psqueue(P, K).
% Create an empty priority search queue.
%
:- func init = psqueue(P, K).
:- pred init(psqueue(P, K)::out) is det.
% True if-and-only-if the priority search queue is empty.
%
:- pred is_empty(psqueue(P, K)::in) is semidet.
% Create a singleton psqueue.
%
:- func singleton(P, K) = psqueue(P, K).
:- pred singleton(P::in, K::in, psqueue(P, K)::out) is det.
% Insert key K with priority P into the given priority search queue.
% Fail if the key already exists.
%
:- pred insert(P::in, K::in, psqueue(P, K)::in, psqueue(P, K)::out) is semidet.
:- pragma type_spec(pred(insert/4), P = int).
% Insert key K with priority P into the given priority search queue.
% Throw an exception if the key already exists.
%
:- func det_insert(psqueue(P, K), P, K) = psqueue(P, K).
:- pred det_insert(P::in, K::in, psqueue(P, K)::in, psqueue(P, K)::out) is det.
:- pragma type_spec(func(det_insert/3), P = int).
:- pragma type_spec(pred(det_insert/4), P = int).
% Return the highest priority priority/key pair in the given queue.
% Fail if the queue is empty.
%
:- pred peek(psqueue(P, K)::in, P::out, K::out) is semidet.
% Return the highest priority priority/key pair in the given queue.
% Throw an exception if the queue is empty.
%
:- pred det_peek(psqueue(P, K)::in, P::out, K::out) is det.
% Remove the element with the top priority. If the queue is empty, fail.
%
:- pred remove_least(P::out, K::out, psqueue(P, K)::in, psqueue(P, K)::out)
is semidet.
:- pragma type_spec(pred(remove_least/4), P = int).
% Remove the element with the top priority. If the queue is empty,
% throw an exception.
%
:- pred det_remove_least(P::out, K::out, psqueue(P, K)::in, psqueue(P, K)::out)
is det.
:- pragma type_spec(pred(det_remove_least/4), P = int).
% Create an association list from a priority search queue.
% The returned list will be in ascending order, sorted first on priority,
% and then on key.
%
:- func to_assoc_list(psqueue(P, K)) = assoc_list(P, K).
:- pred to_assoc_list(psqueue(P, K)::in, assoc_list(P, K)::out) is det.
:- pragma type_spec(func(to_assoc_list/1), P = int).
:- pragma type_spec(pred(to_assoc_list/2), P = int).
% Create a priority search queue from an assoc_list of priority/key pairs.
%
:- func from_assoc_list(assoc_list(P, K)) = psqueue(P, K).
:- pred from_assoc_list(assoc_list(P, K)::in, psqueue(P, K)::out) is det.
:- pragma type_spec(func(from_assoc_list/1), P = int).
:- pragma type_spec(pred(from_assoc_list/2), P = int).
% Remove the element with the given key from a priority queue.
% Fail if it is not in the queue.
%
:- pred remove(P::out, K::in, psqueue(P, K)::in, psqueue(P, K)::out)
is semidet.
:- pragma type_spec(pred(remove/4), P = int).
% Remove the element with the given key from a priority queue.
% Throw an exception if it is not in the queue.
%
:- pred det_remove(P::out, K::in, psqueue(P, K)::in, psqueue(P, K)::out)
is det.
:- pragma type_spec(pred(det_remove/4), P = int).
% Adjust the priority of the specified element; the new priority will be
% the value returned by the given adjustment function on the old priority.
% Fail if the element is not in the queue.
%
:- pred adjust((func(P) = P)::in, K::in, psqueue(P, K)::in, psqueue(P, K)::out)
is semidet.
:- pragma type_spec(pred(adjust/4), P = int).
% Search for the priority of the specified key. If it is not in the queue,
% fail.
%
:- pred search(psqueue(P, K)::in, K::in, P::out) is semidet.
:- pragma type_spec(pred(search/3), P = int).
% Search for the priority of the specified key. If it is not in the queue,
% throw an exception.
%
:- func lookup(psqueue(P, K), K) = P.
:- pred lookup(psqueue(P, K)::in, K::in, P::out) is det.
:- pragma type_spec(func(lookup/2), P = int).
:- pragma type_spec(pred(lookup/3), P = int).
% Return all priority/key pairs whose priority is less than or equal to
% the given priority.
%
:- func at_most(psqueue(P, K), P) = assoc_list(P, K).
:- pred at_most(psqueue(P, K)::in, P::in, assoc_list(P, K)::out) is det.
:- pragma type_spec(func(at_most/2), P = int).
:- pragma type_spec(pred(at_most/3), P = int).
% Return the number of priority/key pairs in the given queue.
%
:- func size(psqueue(P, K)) = int.
:- pred size(psqueue(P, K)::in, int::out) is det.
:- pragma type_spec(func(size/1), P = int).
:- pragma type_spec(pred(size/2), P = int).
%--------------------------------------------------%