Files
mercury/library/psqueue.m
2015-12-12 16:03:50 +11:00

1459 lines
49 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2014 The Mercury Team
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: psqueue.m.
% Main author: Matthias Güdemann.
% Stability: low.
%
% 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 modules provide 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 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 iff 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(insert/4, P = int).
% Insert key K with priority P into the given priority search queue.
% Abort 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(det_insert/3, P = int).
:- pragma type_spec(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(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(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(to_assoc_list/1, P = int).
:- pragma type_spec(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(from_assoc_list/1, P = int).
:- pragma type_spec(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(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(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(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(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(lookup/2, P = int).
:- pragma type_spec(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(at_most/2, P = int).
:- pragma type_spec(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(size/1, P = int).
:- pragma type_spec(size/2, P = int).
%---------------------------------------------------------------------------%
:- implementation.
:- interface.
% The following part of the interface is not for public consumption;
% it is intended only for use by the test suite, e.g. psqueue_test.m
% in tests/hardcoded.
%
% If the implementation is working correctly, then is_semi_heap,
% is_search_tree, has_key_condition and is_finite_map should always succeed.
% Succeed iff the priority search queue respects the semi heap properties:
%
% 1) the top element has the highest priority and
% 2) for each node of the loser tree, the priority of the loser is higher
% or equal to the priorities in the subtree from which the loser
% originates.
%
:- pred is_semi_heap(psqueue(P, K)::in) is semidet.
% Succeed iff the loser tree in the given priority search queue
% respects the search tree properties:
%
% 1) for each node, the keys in the left subtree are smaller than
% or equal to the split key, and
% 2) the keys in the right subtree are greater than the split key.
%
:- pred is_search_tree(psqueue(P, K)::in) is semidet.
% Succeed iff the maximal key in the winner structure and the split keys
% in loser nodes are all present in the search tree.
%
:- pred has_key_condition(psqueue(P, K)::in) is semidet.
% Succeed iff all keys in the queue are present just once.
%
:- pred is_finite_map(psqueue(P, K)::in) is semidet.
% Return a string representation of the queue suitable for debugging.
%
:- func dump_psqueue(psqueue(P, K)) = string.
% Return a string representation of the queue suitable for debugging,
% AFTER checking to see that it passes all four of the above integrity
% tests.
%
:- func verify_and_dump_psqueue(psqueue(P, K)) = string.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module cord.
:- import_module int.
:- import_module io.
:- import_module list.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module string.
%---------------------------------------------------------------------------%
% The psqueue data structure is based on the idea of a knockout tournament
% between the priority/key tuple in the queue. Pairs of priority/key tuples
% play matches, with the loser dropping out of the tournament, while
% the winner plays matches with other winners. Eventually, there is
% only one priority/key tuple left, the champion.
%
% In this view, the champion does not lose any matches, while every other
% tuple loses exactly one match. The representation of psqueues is based
% on this fact. When representing nonempty queues, it stores the champion
% tuple in the w_prio/w_key fields of the winner structure of the
% psqueue type, while it stores all the other tuples in the l_prio/l_key
% fields of loser_node structures in the loser_tree type.
%
% In a binary tree representing a knockout tournament, each winner of a match
% is represented at least twice in the tree: as the winner, and as one of the
% players. The loser_tree type is designed to avoid this redundancy. The idea
% is to have a tree, the loser_tree, whose structure is identical to the
% structure of the binary tree representing the matched of the knockout
% tournament, but to store information about each player in the node
% that corresponds to the match that the player LOST. Since the champion
% does not lose any matches, its details cannot be stored in such a tree,
% which is why they are stored above the tree, in the winner structure.
% The loser_tree type gets its name from the fact that each node stores
% information about the loser of the match it represents (even though
% the loser of that match may have won other matches).
%
% When a psqueue is viewed as a mapping from keys to priorities, the mapping
% must be a function: a key cannot appear in the psqueue more than once.
% When a psqueue is viewed as a mapping from priorities to keys, the mapping
% need not be a function: a priority *may* appear in the psqueue more than
% once.
:- type psqueue(P, K)
---> empty_psqueue
; nonempty_psqueue(winner(P, K)).
:- type winner(P, K)
---> winner(
% The w_prio and w_key fields contain the priority/key pair
% with the lowest numerical priority. If there is more than one
% pair with the same priority, it will contain the pair with
% the smaller key.
w_prio :: P,
w_key :: K,
% The w_losers field contains all the priority/key pairs
% in the priority search queue other than the pair in the
% w_prio/w_key fields.
w_losers :: loser_tree(P, K),
% The w_max_key contains the highest key in the queue;
% it must be equal to either w_key here, or to l_key
% in one of the nodes of the tree in the w_losers field.
% *somewhere* in the entire psqueue. This is first half of
% the *key condition*.
w_max_key :: K
).
:- type loser_tree_size == int.
:- type loser_tree(P, K)
---> loser_leaf
; loser_node(
% The number of priority/key pairs in this loser tree.
% The insertion algorithms use this measure of weight
% to keep the loser tree weight balanced. In our case,
% this means that either both subtrees have at most element,
% or if the ratio of the weights of the two subtrees
% (weight of the heavier subtree divided by the weight
% of the lighter subtree) is no more than the limit ratio
% given the balance_omega function.
l_size :: loser_tree_size,
% The l_prio/l_key pair represents the loser of the match
% that is represented by this node.
%
% The l_prio field must be less than or equal to the priorities
% of all the priority/key pairs in the subtree from which the
% l_prio/l_key pair originates. This loser originates from
% the left subtree (l_left_tree) if l_key is less than or
% equal to l_sort_key; otherwise, it originates from the right
% subtree. This is the *semi-heap condition*.
l_prio :: P,
l_key :: K,
% The l_left_tree field contains all the priority/key pairs
% in this loser tree in which the key is less than or equal to
% the key in the l_sort_key field, while
% the l_right_tree field contains all the priority/key pairs
% in this loser tree in which the key is greater than
% the key in the l_sort_key field. This is the *search tree
% condition*.
%
% The sort key may appear in l_left_tree, or it may be absent
% from l_left_tree. It can never appear in l_right_tree.
%
% The key in l_sort_key must appear as a key (w_key or l_key)
% *somewhere* in the entire psqueue. This is second half of
% the *key condition*.
l_left_tree :: loser_tree(P, K),
l_sort_key :: K,
l_right_tree :: loser_tree(P, K)
).
%---------------------------------------------------------------------------%
%
% This type defines an alternate ways of looking at psqueues: as a tournament
% between priority/key pairs.
%
% Ralf Hinze's paper also talks about two other views, the min view and
% the tree view. We don't need the min view because the one task that it is
% used for in the paper (implementing at_most) we can accomplish more
% efficiently without it, and we don't need the tree view because it is
% isomorphic to the actual representation of loser trees.
:- type tournament_view(P, K)
---> singleton_tournament(P, K)
% A tournament with one entrant.
; tournament_between(winner(P, K), winner(P, K)).
% A tournament between two nonempty sets of entrants.
%
% For tournament_between(WinnerA, WinnerB), all the keys
% in WinnerB will be strictly greater than the maximum key
% in WinnerA.
%---------------------------------------------------------------------------%
% Get a tournament view of a nonempty priority search queue.
%
:- func get_tournament_view(winner(P, K)) = tournament_view(P, K).
:- pragma type_spec(get_tournament_view/1, P = int).
get_tournament_view(Winner) = TournamentView :-
Winner = winner(WinnerPrio, WinnerKey, LTree, MaxKey),
(
LTree = loser_leaf,
TournamentView = singleton_tournament(WinnerPrio, WinnerKey)
;
LTree = loser_node(_, LoserPrio, LoserKey,
SubLTreeL, SplitKey, SubLTreeR),
( if LoserKey `leq` SplitKey then
WinnerA = winner(LoserPrio, LoserKey, SubLTreeL, SplitKey),
WinnerB = winner(WinnerPrio, WinnerKey, SubLTreeR, MaxKey)
else
WinnerA = winner(WinnerPrio, WinnerKey, SubLTreeL, SplitKey),
WinnerB = winner(LoserPrio, LoserKey, SubLTreeR, MaxKey)
),
TournamentView = tournament_between(WinnerA, WinnerB)
),
trace [compile_time(flag("debug_psqueue")), io(!IO)] (
TournamentStr = dump_tournament(0, TournamentView),
io.write_string(TournamentStr, !IO),
io.nl(!IO)
).
% Play a tournament to combine two priority search queues, PSQA and PSQB.
% All the keys in PSQA are guaranteed to be less than or equal to
% PSQA's max key, while all the keys in LTreeR are guaranteed to be
% strictly greater than PSQA's max key.
%
% See Ralf Hinze's paper for a more detailed explanation.
%
% The other combine_*_via_tournament predicates are special cases
% for situations in which we know that one or both psqueues are
% nonempty.
%
:- pred combine_psqueues_via_tournament(psqueue(P, K)::in, psqueue(P, K)::in,
psqueue(P, K)::out) is det.
:- pragma type_spec(combine_psqueues_via_tournament/3, P = int).
combine_psqueues_via_tournament(PSQA, PSQB, CombinedPSQ) :-
(
PSQA = empty_psqueue,
CombinedPSQ = PSQB
% has the same effect as
% (
% PSQB = empty_psqueue,
% CombinedPSQ = empty_psqueue
% ;
% PSQB = nonempty_psqueue(WinnerB),
% CombinedPSQ = PSQB
% )
;
PSQA = nonempty_psqueue(WinnerA),
(
PSQB = empty_psqueue,
CombinedPSQ = PSQA
;
PSQB = nonempty_psqueue(WinnerB),
combine_winners_via_tournament(WinnerA, WinnerB, CombinedWinner),
CombinedPSQ = nonempty_psqueue(CombinedWinner)
)
).
:- pred combine_winner_psqueue_via_tournament(
winner(P, K)::in, psqueue(P, K)::in, winner(P, K)::out) is det.
:- pragma type_spec(combine_winner_psqueue_via_tournament/3, P = int).
combine_winner_psqueue_via_tournament(WinnerA, PSQB, CombinedWinner) :-
(
PSQB = empty_psqueue,
CombinedWinner = WinnerA
;
PSQB = nonempty_psqueue(WinnerB),
combine_winners_via_tournament(WinnerA, WinnerB, CombinedWinner)
).
:- pred combine_psqueue_winner_via_tournament(
psqueue(P, K)::in, winner(P, K)::in, winner(P, K)::out) is det.
:- pragma type_spec(combine_psqueue_winner_via_tournament/3, P = int).
combine_psqueue_winner_via_tournament(PSQA, WinnerB, CombinedWinner) :-
(
PSQA = empty_psqueue,
CombinedWinner = WinnerB
;
PSQA = nonempty_psqueue(WinnerA),
combine_winners_via_tournament(WinnerA, WinnerB, CombinedWinner)
).
:- pred combine_winners_via_tournament(winner(P, K)::in, winner(P, K)::in,
winner(P, K)::out) is det.
:- pragma type_spec(combine_winners_via_tournament/3, P = int).
combine_winners_via_tournament(WinnerA, WinnerB, CombinedWinner) :-
WinnerA = winner(PrioA, KeyA, LTreeA, MaxKeyA),
WinnerB = winner(PrioB, KeyB, LTreeB, MaxKeyB),
( if PrioA `leq` PrioB then
% WinnerA wins
LTree = balance(PrioB, KeyB, LTreeA, MaxKeyA, LTreeB),
CombinedWinner = winner(PrioA, KeyA, LTree, MaxKeyB)
else
% WinnerB wins
LTree = balance(PrioA, KeyA, LTreeA, MaxKeyA, LTreeB),
CombinedWinner = winner(PrioB, KeyB, LTree, MaxKeyB)
).
%---------------------------------------------------------------------------%
%
% Balancing functions for weight balanced trees.
%
% balance(Prio, Key, LTreeL, SplitKey, LTreeR) = LTree:
%
% Construct LTree, a loser tree that contains:
%
% - the priority/key pair Prio/Key,
% - all the priority/key pairs in LTreeL, and
% - all the priority/key pairs in LTreeR.
%
% All the keys in LTreeL are guaranteed to be less than or equal to
% SplitKey, while all the keys in LTreeR are guaranteed to be strictly
% greater than SplitKey.
%
% XXX It would be nice to add assertions to the code to enforce this
% invariant, and see whether they are violated during stress tests
% of this module. However, right now we don't have any such stress tests.
%
% NOTE All the calls to unexpected below when finding loser_leaf are there
% because there is no way to tell Mercury that finding that e.g. LTreeL
% is heavier than LTreeR implies that LTreeL cannot be empty.
%
:- func balance(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(balance/5, P = int).
balance(Prio, Key, LTreeL, SplitKey, LTreeR) = LTree :-
SizeL = loser_tree_size(LTreeL),
SizeR = loser_tree_size(LTreeR),
( if
SizeR + SizeL < 2
then
LTree = construct_node(Prio, Key, LTreeL, SplitKey, LTreeR)
else if
compare(CMPL, SizeR, balance_omega * SizeL),
CMPL = (>)
then
LTree = balance_left(Prio, Key, LTreeL, SplitKey, LTreeR)
else if
compare(CMPR, SizeL, balance_omega * SizeR),
CMPR = (>)
then
LTree = balance_right(Prio, Key, LTreeL, SplitKey, LTreeR)
else
LTree = construct_node(Prio, Key, LTreeL, SplitKey, LTreeR)
).
% The implementation of balance for the case when
% size(LTreeR) > balance_omega * size(LTreeL),
% so we want to rotate the tree to move weight towards the left.
%
:- func balance_left(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(balance_left/5, P = int).
balance_left(Prio, Key, LTreeL, SplitKey, LTreeR) = LTree :-
(
LTreeR = loser_node(_, _, _, SubLTreeRL, _, SubLTreeRR),
compare(CMP, loser_tree_size(SubLTreeRL), loser_tree_size(SubLTreeRR)),
( if CMP = (<) then
LTree = single_left(Prio, Key, LTreeL, SplitKey, LTreeR)
else
LTree = double_left(Prio, Key, LTreeL, SplitKey, LTreeR)
)
;
LTreeR = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% The implementation of balance for the case when
% size(LTreeL) > balance_omega * size(LTreeR).
% so we want to rotate the tree to move weight towards the right.
%
:- func balance_right(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(balance_right/5, P = int).
balance_right(Prio, Key, LTreeL, SplitKey, LTreeR) = LTree :-
(
LTreeL = loser_node(_, _, _, SubLTreeLL, _, SubLTreeLR),
compare(CMP, loser_tree_size(SubLTreeLR), loser_tree_size(SubLTreeLL)),
( if CMP = (<) then
LTree = single_right(Prio, Key, LTreeL, SplitKey, LTreeR)
else
LTree = double_right(Prio, Key, LTreeL, SplitKey, LTreeR)
)
;
LTreeL = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% The implementation of balance for the case when we need a double
% rotation to the left.
%
:- func double_left(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(double_left/5, P = int).
double_left(InsertPrio, InsertKey, LTreeA, SplitKeyAB, LTreeBC) = LTree :-
(
LTreeBC = loser_node(_, LoserPrio, LoserKey,
LTreeB, SplitKeyBC, LTreeC),
LTree = single_left(InsertPrio, InsertKey,
LTreeA,
SplitKeyAB,
single_right(LoserPrio, LoserKey, LTreeB, SplitKeyBC, LTreeC))
;
LTreeBC = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% The implementation of balance for the case when we need a double
% rotation to the right.
%
:- func double_right(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(double_right/5, P = int).
double_right(InsertPrio, InsertKey, LTreeAB, SplitKeyBC, LTreeC) = LTree :-
(
LTreeAB = loser_node(_, LoserPrio, LoserKey,
LTreeA, SplitKeyAB, LTreeB),
LTree = single_right(InsertPrio, InsertKey,
single_left(LoserPrio, LoserKey, LTreeA, SplitKeyAB, LTreeB),
SplitKeyBC,
LTreeC)
;
LTreeAB = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% The implementation of balance for the case when we need a single
% rotation to the left.
%
:- func single_left(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(single_left/5, P = int).
single_left(InsertPrio, InsertKey, LTreeA, SplitKeyAB, LTreeBC) = LTree :-
(
LTreeBC = loser_node(_, LoserPrio, LoserKey,
LTreeB, SplitKeyBC, LTreeC),
( if
LoserKey `leq` SplitKeyBC,
InsertPrio `leq` LoserPrio
then
LTree = construct_node(InsertPrio, InsertKey,
construct_node(LoserPrio, LoserKey,
LTreeA, SplitKeyAB, LTreeB),
SplitKeyBC,
LTreeC)
else
LTree = construct_node(LoserPrio, LoserKey,
construct_node(InsertPrio, InsertKey,
LTreeA, SplitKeyAB, LTreeB),
SplitKeyBC,
LTreeC)
)
;
LTreeBC = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% The implementation of balance for the case when we need a single
% rotation to the right.
%
:- func single_right(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma type_spec(single_right/5, P = int).
single_right(InsertPrio, InsertKey, LTreeAB, SplitKeyBC, LTreeC) = LTree :-
(
LTreeAB = loser_node(_, LoserPrio, LoserKey,
LTreeA, SplitKeyAB, LTreeB),
( if
compare(CMP0, LoserKey, SplitKeyAB),
CMP0 = (>),
InsertPrio `leq` LoserPrio
then
LTree = construct_node(InsertPrio, InsertKey,
LTreeA,
SplitKeyAB,
construct_node(LoserPrio, LoserKey,
LTreeB, SplitKeyBC, LTreeC))
else
LTree = construct_node(LoserPrio, LoserKey,
LTreeA,
SplitKeyAB,
construct_node(InsertPrio, InsertKey,
LTreeB, SplitKeyBC, LTreeC))
)
;
LTreeAB = loser_leaf,
unexpected($file, $pred, "heavier tree is a leaf")
).
% Balance factor, must be over 3.75 (see Ralf Hinze's paper).
%
:- func balance_omega = loser_tree_size.
balance_omega = 4.
:- func construct_node(P, K, loser_tree(P, K), K, loser_tree(P, K))
= loser_tree(P, K).
:- pragma inline(construct_node/5).
construct_node(Prio, Key, SubLTreeL, SplitKey, SubLTreeR) = LTree :-
Size = 1 + loser_tree_size(SubLTreeL) + loser_tree_size(SubLTreeR),
LTree = loser_node(Size, Prio, Key, SubLTreeL, SplitKey, SubLTreeR).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
init = PSQ :-
init(PSQ).
init(empty_psqueue).
is_empty(empty_psqueue).
singleton(Prio, Key) = PSQ :-
singleton(Prio, Key, PSQ).
singleton(Prio, Key, PSQ) :-
PSQ = nonempty_psqueue(singleton_winner(Prio, Key)).
:- func singleton_winner(P, K) = winner(P, K).
:- pragma inline(singleton_winner/2).
singleton_winner(Prio, Key) =
winner(Prio, Key, loser_leaf, Key).
%---------------------------------------------------------------------------%
insert(InsertPrio, InsertKey, !PSQ) :-
(
!.PSQ = empty_psqueue,
!:PSQ = singleton(InsertPrio, InsertKey)
;
!.PSQ = nonempty_psqueue(Winner0),
insert_tv(InsertPrio, InsertKey, get_tournament_view(Winner0), Winner),
!:PSQ = nonempty_psqueue(Winner)
).
det_insert(PSQ0, InsertPrio, InsertKey) = PSQ :-
det_insert(InsertPrio, InsertKey, PSQ0, PSQ).
det_insert(InsertPrio, InsertKey, !PSQ) :-
( if insert(InsertPrio, InsertKey, !PSQ) then
true
else
unexpected($file, $pred, "key being inserted is already present")
).
:- pred insert_tv(P::in, K::in,
tournament_view(P, K)::in, winner(P, K)::out) is semidet.
:- pragma type_spec(insert_tv/4, P = int).
insert_tv(InsertPrio, InsertKey, TV, Winner) :-
(
TV = singleton_tournament(Prio, Key),
compare(CMP, InsertKey, Key),
(
CMP = (<),
WinnerA = singleton_winner(InsertPrio, InsertKey),
WinnerB = singleton_winner(Prio, Key)
;
CMP = (>),
WinnerA = singleton_winner(Prio, Key),
WinnerB = singleton_winner(InsertPrio, InsertKey)
),
% XXX Why call a general-purpose predicate for combining
% two singletons?
combine_winners_via_tournament(WinnerA, WinnerB, Winner)
;
TV = tournament_between(WinnerA, WinnerB),
WinnerA = winner(_, _, _, MaxKeyA),
WinnerB = winner(_, _, _, _),
( if InsertKey `leq` MaxKeyA then
insert_tv(InsertPrio, InsertKey,
get_tournament_view(WinnerA), UpdatedWinnerA),
combine_winners_via_tournament(UpdatedWinnerA, WinnerB, Winner)
else
insert_tv(InsertPrio, InsertKey,
get_tournament_view(WinnerB), UpdatedWinnerB),
combine_winners_via_tournament(WinnerA, UpdatedWinnerB, Winner)
)
).
%---------------------------------------------------------------------------%
peek(PSQ, MinPrio, MinKey) :-
PSQ = nonempty_psqueue(winner(MinPrio, MinKey, _, _)).
det_peek(PSQ, MinPrio, MinKey) :-
( if peek(PSQ, MinPrioPrime, MinKeyPrime) then
MinKey = MinKeyPrime,
MinPrio = MinPrioPrime
else
unexpected($file, $pred, "priority search queue is empty")
).
remove_least(MinPrio, MinKey, !PSQ) :-
!.PSQ = nonempty_psqueue(winner(MinPrio, MinKey, LTree, MaxKey)),
!:PSQ = convert_loser_tree_to_psqueue(LTree, MaxKey).
det_remove_least(MinPrio, MinKey, !PSQ) :-
( if remove_least(MinPrioPrime, MinKeyPrime, !PSQ) then
MinKey = MinKeyPrime,
MinPrio = MinPrioPrime
else
unexpected($file, $pred, "priority search queue is empty")
).
% convert_loser_tree_to_psqueue(LTree, MaxKey):
%
% Convert LTree to a psqueue. All the keys in LTree are guaranteed
% to be less than or equal to MaxKey.
%
:- func convert_loser_tree_to_psqueue(loser_tree(P, K), K) = psqueue(P, K).
:- pragma type_spec(convert_loser_tree_to_psqueue/2, P = int).
convert_loser_tree_to_psqueue(LTree, MaxKey) = PSQ :-
(
LTree = loser_leaf,
PSQ = empty_psqueue
;
LTree = loser_node(_, LoserPrio, LoserKey,
SubLTreeL, SplitKey, SubLTreeR),
( if LoserKey `leq` SplitKey then
WinnerA = winner(LoserPrio, LoserKey, SubLTreeL, SplitKey),
PSQA = nonempty_psqueue(WinnerA),
PSQB = convert_loser_tree_to_psqueue(SubLTreeR, MaxKey)
else
PSQA = convert_loser_tree_to_psqueue(SubLTreeL, SplitKey),
WinnerB = winner(LoserPrio, LoserKey, SubLTreeR, MaxKey),
PSQB = nonempty_psqueue(WinnerB)
),
combine_psqueues_via_tournament(PSQA, PSQB, PSQ)
).
%---------------------------------------------------------------------------%
to_assoc_list(PSQ) = AssocList :-
to_assoc_list(PSQ, AssocList).
to_assoc_list(PSQ0, AssocList) :-
( if remove_least(K, P, PSQ0, PSQ1) then
to_assoc_list(PSQ1, AssocListTail),
AssocList = [K - P | AssocListTail]
else
AssocList = []
).
from_assoc_list(AssocList) = PSQ :-
from_assoc_list(AssocList, PSQ).
from_assoc_list(AssocList, PSQ) :-
from_assoc_list_loop(AssocList, init, PSQ).
:- pred from_assoc_list_loop(assoc_list(P, K)::in,
psqueue(P, K)::in, psqueue(P, K)::out) is det.
:- pragma type_spec(from_assoc_list_loop/3, P = int).
from_assoc_list_loop([], !PSQ).
from_assoc_list_loop([Prio - Key | PriosKeys], !PSQ) :-
det_insert(Prio, Key, !PSQ),
from_assoc_list_loop(PriosKeys, !PSQ).
%---------------------------------------------------------------------------%
remove(MatchingPrio, SearchKey, !PSQ) :-
(
!.PSQ = empty_psqueue,
fail
;
!.PSQ = nonempty_psqueue(Winner0),
remove_tv(MatchingPrio, SearchKey, get_tournament_view(Winner0), !:PSQ)
).
det_remove(MatchingPrio, SearchKey, !PSQ) :-
( if remove(MatchingPrioPrime, SearchKey, !PSQ) then
MatchingPrio = MatchingPrioPrime
else
unexpected($file, $pred, "element not found")
).
:- pred remove_tv(P::out, K::in,
tournament_view(P, K)::in, psqueue(P, K)::out) is semidet.
:- pragma type_spec(remove_tv/4, P = int).
remove_tv(MatchingPrio, SearchKey, TournamentView, PSQ) :-
(
TournamentView = singleton_tournament(Prio, Key),
( if Key = SearchKey then
MatchingPrio = Prio,
PSQ = empty_psqueue
else
fail
)
;
TournamentView = tournament_between(WinnerA, WinnerB),
WinnerA = winner(_, _, _, MaxKeyA),
( if SearchKey `leq` MaxKeyA then
remove_tv(MatchingPrio, SearchKey,
get_tournament_view(WinnerA), UpdatedPSQA),
combine_psqueue_winner_via_tournament(UpdatedPSQA, WinnerB,
CombinedWinner)
else
remove_tv(MatchingPrio, SearchKey,
get_tournament_view(WinnerB), UpdatedPSQB),
combine_winner_psqueue_via_tournament(WinnerA, UpdatedPSQB,
CombinedWinner)
),
PSQ = nonempty_psqueue(CombinedWinner)
).
%---------------------------------------------------------------------------%
adjust(AdjustFunc, SearchKey, !PSQ) :-
(
!.PSQ = empty_psqueue,
fail
;
!.PSQ = nonempty_psqueue(Winner0),
adjust_tv(AdjustFunc, SearchKey, get_tournament_view(Winner0), Winner),
!:PSQ = nonempty_psqueue(Winner)
).
:- pred adjust_tv(func(P) = P::in(func(in) = out is det),
K::in, tournament_view(P, K)::in, winner(P, K)::out) is semidet.
:- pragma type_spec(adjust_tv/4, P = int).
adjust_tv(AdjustFunc, SearchKey, TournamentView, Winner) :-
(
TournamentView = singleton_tournament(Prio, Key),
( if Key = SearchKey then
Winner = singleton_winner(AdjustFunc(Prio), Key)
else
% XXX was Winner = singleton_winner(Prio, Key)
fail
)
;
TournamentView = tournament_between(WinnerA, WinnerB),
WinnerA = winner(_, _, _, MaxKeyA),
( if SearchKey `leq` MaxKeyA then
adjust_tv(AdjustFunc, SearchKey,
get_tournament_view(WinnerA), UpdatedWinnerA),
combine_winners_via_tournament(UpdatedWinnerA, WinnerB, Winner)
else
adjust_tv(AdjustFunc, SearchKey,
get_tournament_view(WinnerB), UpdatedWinnerB),
combine_winners_via_tournament(WinnerA, UpdatedWinnerB, Winner)
)
).
%---------------------------------------------------------------------------%
search(PSQ, SearchKey, MatchingPrio) :-
(
PSQ = empty_psqueue,
fail
;
PSQ = nonempty_psqueue(Winner),
% XXX Why do we transform PSQ into a tournament view
% before searching it? Why don't we search it directly?
% It should be both simpler and faster.
search_tv(get_tournament_view(Winner), SearchKey, MatchingPrio)
).
:- pred search_tv(tournament_view(P, K)::in, K::in, P::out) is semidet.
:- pragma type_spec(search_tv/3, P = int).
search_tv(TournamentView, SearchKey, MatchingPrio) :-
(
TournamentView = singleton_tournament(Prio, Key),
( if Key = SearchKey then
MatchingPrio = Prio
else
fail
)
;
TournamentView = tournament_between(WinnerA, WinnerB),
WinnerA = winner(_, _, _, MaxKeyA),
( if SearchKey `leq` MaxKeyA then
search_tv(get_tournament_view(WinnerA), SearchKey, MatchingPrio)
else
search_tv(get_tournament_view(WinnerB), SearchKey, MatchingPrio)
)
).
lookup(PSQ, SearchKey) = MatchingPrio :-
lookup(PSQ, SearchKey, MatchingPrio).
lookup(PSQ, SearchKey, MatchingPrio) :-
( if search(PSQ, SearchKey, MatchingPrioPrime) then
MatchingPrio = MatchingPrioPrime
else
unexpected($file, $pred, "key not found")
).
%---------------------------------------------------------------------------%
at_most(PSQ, MaxPrio) = AssocList :-
at_most(PSQ, MaxPrio, AssocList).
at_most(PSQ, MaxPrio, AssocList) :-
(
PSQ = empty_psqueue,
AssocList = []
;
PSQ = nonempty_psqueue(Winner),
at_most_in_winner(Winner, MaxPrio, Cord),
AssocList = cord.list(Cord)
).
:- pred at_most_in_winner(winner(P, K)::in, P::in, cord(pair(P, K))::out)
is det.
:- pragma type_spec(at_most_in_winner/3, P = int).
at_most_in_winner(Winner, MaxPrio, Cord) :-
Winner = winner(WinnerPrio, _, _, _),
compare(CMP, WinnerPrio, MaxPrio),
(
CMP = (>),
Cord = cord.init
;
( CMP = (=)
; CMP = (<)
),
TournamentView = get_tournament_view(Winner),
(
TournamentView = singleton_tournament(SinglePrio, SingleKey),
Cord = cord.singleton(SinglePrio - SingleKey)
;
TournamentView = tournament_between(WinnerA, WinnerB),
at_most_in_winner(WinnerA, MaxPrio, CordA),
at_most_in_winner(WinnerB, MaxPrio, CordB),
Cord = CordA ++ CordB
)
).
%---------------------------------------------------------------------------%
size(PSQ) = Size :-
size(PSQ, Size).
size(PSQ, Size) :-
(
PSQ = empty_psqueue,
Size = 0
;
PSQ = nonempty_psqueue(winner(_, _, LTree, _)),
% XXX was just loser_tree_size(LTree)
Size = 1 + loser_tree_size(LTree)
).
:- func loser_tree_size(loser_tree(P, K)) = loser_tree_size.
loser_tree_size(LTree) = Size :-
(
LTree = loser_leaf,
Size = 0
;
LTree = loser_node(Size, _, _, _, _, _)
).
%---------------------------------------------------------------------------%
:- pred leq(V::in, V::in) is semidet.
:- pragma type_spec(leq/2, V = int).
leq(ValLeft, ValRight) :-
compare(CMP, ValLeft, ValRight),
( CMP = (<)
; CMP = (=)
).
:- func min2(V, V) = V.
:- pragma type_spec(min2/2, V = int).
min2(A, B) = Min :-
( if A `leq` B then
Min = A
else
Min = B
).
:- func max2(V, V) = V.
:- pragma type_spec(max2/2, V = int).
max2(A, B) = Max :-
( if A `leq` B then
Max = B
else
Max = A
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The integrity test predicates.
%
is_semi_heap(PSQ) :-
(
PSQ = empty_psqueue
;
PSQ = nonempty_psqueue(winner(WinnerPrio, _, LTree, _)),
all_prios_in_loser_tree_at_or_above_prio(WinnerPrio, LTree),
all_nodes_obey_semi_heap(LTree)
).
% Succeed iff all priorities in the given ltree are numerically
% at least as high as the given priority.
%
:- pred all_prios_in_loser_tree_at_or_above_prio(P::in, loser_tree(P, K)::in)
is semidet.
all_prios_in_loser_tree_at_or_above_prio(WinnerPrio, LTree) :-
(
LTree = loser_leaf
;
LTree = loser_node(_, LoserPrio, _, SubLTreeL, _, SubLTreeR),
WinnerPrio `leq` LoserPrio,
all_prios_in_loser_tree_at_or_above_prio(WinnerPrio, SubLTreeL),
all_prios_in_loser_tree_at_or_above_prio(WinnerPrio, SubLTreeR)
).
:- pred all_nodes_obey_semi_heap(loser_tree(P, K)::in) is semidet.
all_nodes_obey_semi_heap(LTree) :-
(
LTree = loser_leaf
;
LTree = loser_node(_, Prio, Key, SubLTreeL, SplitKey, SubLTreeR),
( if Key `leq` SplitKey then
min_prio_in_loser_tree_acc(SubLTreeL, Prio, MaybeMinPrio)
else
min_prio_in_loser_tree_acc(SubLTreeR, Prio, MaybeMinPrio)
),
(
MaybeMinPrio = no
;
MaybeMinPrio = yes(MinPrio),
compare(CMP, Prio, MinPrio),
CMP = (=)
),
all_nodes_obey_semi_heap(SubLTreeL),
all_nodes_obey_semi_heap(SubLTreeR)
).
:- pred min_prio_in_loser_tree_acc(loser_tree(P, K)::in, P::in, maybe(P)::out)
is det.
min_prio_in_loser_tree_acc(LTree, !.CurMinPrio, MaybeMinPrio) :-
(
LTree = loser_leaf,
MaybeMinPrio = no
;
LTree = loser_node(_, Prio, _, SubLTreeL, _, SubLTreeR),
compare(CMP, !.CurMinPrio, Prio),
(
( CMP = (<)
; CMP = (=)
)
;
CMP = (>),
!:CurMinPrio = Prio
),
min_prio_in_loser_tree_acc(SubLTreeL, !.CurMinPrio, MaybeMinPrioL),
min_prio_in_loser_tree_acc(SubLTreeR, !.CurMinPrio, MaybeMinPrioR),
take_min_xmxmx(!.CurMinPrio, MaybeMinPrioL, MaybeMinPrioR, MinPrio),
MaybeMinPrio = yes(MinPrio)
).
%---------------------------------------------------------------------------%
is_search_tree(PSQ) :-
(
PSQ = empty_psqueue
;
PSQ = nonempty_psqueue(winner(_, _, LTree, _)),
loser_tree_has_search_property(LTree)
).
:- pred loser_tree_has_search_property(loser_tree(P, K)::in) is semidet.
loser_tree_has_search_property(LTree) :-
(
LTree = loser_leaf
;
LTree = loser_node(_, _, _, SubLTreeL, SplitKey, SubLTreeR),
max_key_in_loser_tree(SubLTreeL, MaybeMaxKeyL),
min_key_in_loser_tree(SubLTreeR, MaybeMinKeyR),
(
MaybeMaxKeyL = no
;
MaybeMaxKeyL = yes(MaxKeyL),
compare(CMPL, MaxKeyL, SplitKey),
( CMPL = (<) ; CMPL = (=) ),
loser_tree_has_search_property(SubLTreeL)
),
(
MaybeMinKeyR = no
;
MaybeMinKeyR = yes(MinKeyR),
compare(CMPR, MinKeyR, SplitKey),
CMPR = (>),
loser_tree_has_search_property(SubLTreeR)
)
).
%------------------%
:- pred min_key_in_loser_tree(loser_tree(P, K)::in, maybe(K)::out) is det.
min_key_in_loser_tree(LTree, MaybeMinKey) :-
(
LTree = loser_leaf,
MaybeMinKey = no
;
LTree = loser_node(_, _, Key, SubLTreeL, _, SubLTreeR),
CurMin = Key,
min_key_in_loser_tree_acc(SubLTreeL, CurMin, MaybeMinKeyL),
min_key_in_loser_tree_acc(SubLTreeR, CurMin, MaybeMinKeyR),
take_min_xmxmx(CurMin, MaybeMinKeyL, MaybeMinKeyR, MinKey),
MaybeMinKey = yes(MinKey)
).
:- pred min_key_in_loser_tree_acc(loser_tree(P, K)::in, K::in,
maybe(K)::out) is det.
min_key_in_loser_tree_acc(LTree, !.CurMin, MaybeMinKey) :-
(
LTree = loser_leaf,
MaybeMinKey = no
;
LTree = loser_node(_, _, Key, SubLTreeL, _, SubLTreeR),
compare(CMP, !.CurMin, Key),
(
( CMP = (<)
; CMP = (=)
)
;
CMP = (>),
!:CurMin = Key
),
min_key_in_loser_tree_acc(SubLTreeL, !.CurMin, MaybeMinKeyL),
min_key_in_loser_tree_acc(SubLTreeR, !.CurMin, MaybeMinKeyR),
take_min_xmxmx(!.CurMin, MaybeMinKeyL, MaybeMinKeyR, MinKey),
MaybeMinKey = yes(MinKey)
).
%------------------%
:- pred max_key_in_loser_tree(loser_tree(P, K)::in, maybe(K)::out) is det.
max_key_in_loser_tree(LTree, MaybeMaxKey) :-
(
LTree = loser_leaf,
MaybeMaxKey = no
;
LTree = loser_node(_, _, Key, SubLTreeL, _, SubLTreeR),
CurMax = Key,
max_key_in_loser_tree_acc(SubLTreeL, CurMax, MaybeMaxKeyL),
max_key_in_loser_tree_acc(SubLTreeR, CurMax, MaybeMaxKeyR),
take_max_xmxmx(CurMax, MaybeMaxKeyL, MaybeMaxKeyR, MaxKey),
MaybeMaxKey = yes(MaxKey)
).
:- pred max_key_in_loser_tree_acc(loser_tree(P, K)::in, K::in,
maybe(K)::out) is det.
max_key_in_loser_tree_acc(LTree, !.CurMax, MaybeMaxKey) :-
(
LTree = loser_leaf,
MaybeMaxKey = no
;
LTree = loser_node(_, _, Key, SubLTreeL, _, SubLTreeR),
compare(CMP, !.CurMax, Key),
(
CMP = (<),
!:CurMax = Key
;
( CMP = (=)
; CMP = (>)
)
),
max_key_in_loser_tree_acc(SubLTreeL, !.CurMax, MaybeMaxKeyL),
max_key_in_loser_tree_acc(SubLTreeR, !.CurMax, MaybeMaxKeyR),
take_max_xmxmx(!.CurMax, MaybeMaxKeyL, MaybeMaxKeyR, MaxKey),
MaybeMaxKey = yes(MaxKey)
).
%------------------%
:- pred take_min_xmxmx(T::in, maybe(T)::in, maybe(T)::in, T::out) is det.
take_min_xmxmx(X, MaybeL, MaybeR, Min) :-
(
MaybeL = no,
MaybeR = no,
Min = X
;
MaybeL = yes(L),
MaybeR = no,
Min = min2(X, L)
;
MaybeL = no,
MaybeR = yes(R),
Min = min2(X, R)
;
MaybeL = yes(L),
MaybeR = yes(R),
Min = min2(X, min2(L, R))
).
:- pred take_max_xmxmx(T::in, maybe(T)::in, maybe(T)::in, T::out) is det.
take_max_xmxmx(X, MaybeL, MaybeR, Min) :-
(
MaybeL = no,
MaybeR = no,
Min = X
;
MaybeL = yes(L),
MaybeR = no,
Min = max2(X, L)
;
MaybeL = no,
MaybeR = yes(R),
Min = max2(X, R)
;
MaybeL = yes(L),
MaybeR = yes(R),
Min = max2(X, max2(L, R))
).
%---------------------------------------------------------------------------%
has_key_condition(PSQ) :-
(
PSQ = empty_psqueue
;
PSQ = nonempty_psqueue(winner(_, _, LTree, MaxKey)),
search(PSQ, MaxKey, _),
loser_split_keys_are_present(PSQ, LTree)
).
% loser_split_keys_are_present(PSQ, LTree):
%
% Succeed iff all the split keys in LTree are present *somewhere* in PSQ.
% They do not have to be present in LTree itself, and in fact, they often
% won't be.
%
:- pred loser_split_keys_are_present(psqueue(P, K)::in, loser_tree(P, K)::in)
is semidet.
loser_split_keys_are_present(PSQ, LTree) :-
(
LTree = loser_leaf
;
LTree = loser_node(_, _, _, SubLTreeL, SplitKey, SubLTreeR),
search(PSQ, SplitKey, _),
loser_split_keys_are_present(PSQ, SubLTreeL),
loser_split_keys_are_present(PSQ, SubLTreeR)
).
%---------------------------------------------------------------------------%
is_finite_map(PSQ) :-
(
PSQ = empty_psqueue
;
PSQ = nonempty_psqueue(Winner),
Winner = winner(_, _, LTree, _),
Keys = get_keys(LTree),
list.sort_and_remove_dups(Keys, UniqKeys),
list.length(Keys, NumKeys),
list.length(UniqKeys, NumUniqKeys),
NumKeys = NumUniqKeys
).
% Return a list of the keys in the given loser tree, in no particular
% order.
%
:- func get_keys(loser_tree(P, K)) = list(K).
get_keys(LTree) = Keys :-
(
LTree = loser_leaf,
Keys = []
;
LTree = loser_node(_, _, Key, SubLTreeL, _, SubLTreeR),
Keys = [Key | get_keys(SubLTreeL) ++ get_keys(SubLTreeR)]
).
%---------------------------------------------------------------------------%
dump_psqueue(PSQ) =
dump_psqueue(0, PSQ).
verify_and_dump_psqueue(PSQ) = Str :-
( if
is_semi_heap(PSQ),
is_search_tree(PSQ),
has_key_condition(PSQ),
is_finite_map(PSQ)
then
Str = dump_psqueue(PSQ)
else
unexpected($module, $pred, "verification failed")
).
:- func indent_string(int) = string.
indent_string(Indent) = IndentStr :-
string.duplicate_char(' ', Indent * 2, IndentStr).
:- func dump_psqueue(int, psqueue(P, K)) = string.
dump_psqueue(Indent, PSQ) = Str :-
IndentStr = indent_string(Indent),
(
PSQ = empty_psqueue,
Str = IndentStr ++ "void"
;
PSQ = nonempty_psqueue(Winner),
Str = dump_winner(Indent, Winner)
).
:- func dump_winner(int, winner(P, K)) = string.
dump_winner(Indent, Winner) = Str :-
IndentStr = indent_string(Indent),
Winner = winner(WinnerPrio, WinnerKey, LTree, MaxKey),
Str = IndentStr ++ "winner(prio " ++ string(WinnerPrio) ++
", key " ++ string(WinnerKey) ++
", maxkey " ++ string(MaxKey) ++ ",\n" ++
dump_loser_tree(Indent + 1, LTree) ++
IndentStr ++ ")\n".
:- func dump_loser_tree(int, loser_tree(P, K)) = string.
dump_loser_tree(Indent, LTree) = Str :-
IndentStr = indent_string(Indent),
(
LTree = loser_leaf,
Str = IndentStr ++ "loser_leaf\n"
;
LTree = loser_node(Size, LoserPrio, LoserKey,
LTreeL, SplitKey, LTreeR),
Str = IndentStr ++ "loser_node(size " ++ int_to_string(Size) ++
", prio " ++ string(LoserPrio) ++
", key " ++ string(LoserKey) ++ "\n" ++
dump_loser_tree(Indent + 1, LTreeL) ++
IndentStr ++ "split key " ++ string(SplitKey) ++ "\n" ++
dump_loser_tree(Indent + 1, LTreeR) ++
IndentStr ++ ")\n"
).
:- func dump_tournament(int, tournament_view(P, K)) = string.
dump_tournament(Indent, Tournament) = Str :-
IndentStr = indent_string(Indent),
(
Tournament = singleton_tournament(Prio, Key),
Str = IndentStr ++ "singleton_tournament(key " ++ string(Key) ++
", prio " ++ string(Prio) ++ ")\n"
;
Tournament = tournament_between(WinnerA, WinnerB),
Str = IndentStr ++ "tournament_between(\n" ++
dump_winner(Indent + 1, WinnerA) ++
IndentStr ++ "and\n" ++
dump_winner(Indent + 1, WinnerB) ++
IndentStr ++ ")\n"
).
%---------------------------------------------------------------------------%
:- end_module psqueue.
%---------------------------------------------------------------------------%