%---------------------------------------------------------------------------%
% Copyright (C) 1994-1997, 1999-2000 The University of Melbourne.
% Copyright (C) 2000 Imperial College London.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB.
%---------------------------------------------------------------------------%

% m_tree234 - implements a map (dictionary) using 2-3-4 trees.
% main author: conway@cs.mu.OZ.AU.
% stability: medium.

% Modified for ECLiPSe by Warwick Harvey <wh@icparc.ic.ac.uk>, April 2000,
% based on revision 1.28 of file mercury/library/tree234.m from the
% Mercury CVS repository.  See http://www.cs.mu.oz.au/mercury for
% information about obtaining Mercury.

% The conversion included stripping out the functional versions of
% predicates and the higher-order predicates.  It also included providing
% user-level documentation.

% Note that the elimination of choicepoints in the ECLiPSe version was done
% semi-automatically; hence the names of the introduced predicates are not
% particularly imaginitive.

% This module assumes that keys are ground (so they can't be modified after
% insertion into the tree), but allows the values stored to be variables.

%---------------------------------------------------------------------------%

:- module(m_tree234).

:- export
	init/1,
	is_empty/1,
	is_tree234/1,
	member/3,
	search/3,
	lookup/3,
	lower_bound_search/4,
	lower_bound_lookup/4,
	upper_bound_search/4,
	upper_bound_lookup/4,
	insert/4,
	set/4,
	delete/3,
	remove/4,
	remove_smallest/4,
	keys/2,
	values/2,
	update/4,
	count/2,
	assoc_list_to_tree234/2,
	tree234_to_assoc_list/2.

:- comment(categories, ["Data Structures"]).
:- comment(summary, "A map (dictionary) implemented using 2-3-4 trees").
:- comment(author, "Thomas Conway (Mercury) and Warwick Harvey (ECLiPSe)").
:- comment(desc, html("\
	<P>
	This module implements 2-3-4 trees.  They can be used to manage and
	index a collection of key/value pairs.  You probably want to use the
	interface provided by `map.pl' instead of using this module
	directly.
	</P>
	<P>
	Note that keys must be ground, but values are allowed to be
	variables.
	</P>
	")).

% :- pred init(tree234(K, V)).
% :- mode init(uo) is det.
:- comment(init/1, [
	amode:		init(-),
	args:		["Tree":"The new tree"],
	summary:	"Initialise a new (empty) 2-3-4 tree.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[is_empty/1]
]).

% :- mode is_tree234(in) is semidet.
:- comment(is_tree234/1, [
	amode:          is_tree234(?), 
	args:           ["Tree":"A term"],
	summary:        "Check whether argument is a 2-3-4 tree.",
	fail_if:        "Fails if Tree is not a 2-3-4 tree.",
	resat:          no,
	see_also:       [init/1]
]).

% :- pred is_empty(tree234(K, V)).
% :- mode is_empty(in) is semidet.
:- comment(is_empty/1, [
	amode:          is_empty(?), 
	args:           ["Tree":"A 2-3-4 tree"],
	summary:        "Check whether a tree is empty.",
	fail_if:        "Fails if Tree is not an empty tree.",
	resat:          no,
	see_also:       [init/1]
]).

% :- pred member(tree234(K, V), K, V).
% :- mode member(in, out, out) is nondet.
:- comment(member/3, [
	amode:		member(+, ?, ?),
	args:		["Tree":"A 2-3-4 tree",
			"Key":"A key from Tree",
			"Value":"The value in Tree corresponding to Key"],
	summary:	"Succeeds if Key and Value unify with a key/value pair from Tree.",
	fail_if:	"Fails if Key and Value do not unify with a key/value pair from Tree.",
	resat:		yes,
	see_also:	[search/3, lookup/3],
	desc:		html("\
	<P>
	Tries to unify Key and Value with key/value pairs from the tree Tree.
	</P>
	<P>
	If Key and Value are variables and Tree is a 2-3-4 tree, then all
	members of the tree Tree are found on backtracking.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred search(tree234(K, V), K, V).
% :- mode search(in, in, out) is semidet.
:- comment(search/3, [
	amode:		search(+, ++, ?),
	args:		["Tree":"A 2-3-4 tree",
			"Key":"A key to search for",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for a key.",
	fail_if:	"Fails if Key does not appear in Tree or if Value does not unify with the corresponding value found.",
	resat:		no,
	see_also:	[member/3, lookup/3],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for an entry with key Key.
	If the key is found, then it attempts to unify the corresponding
	value with Value.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred lookup(tree234(K, V), K, V).
% :- mode lookup(in, in, out) is det.
:- comment(lookup/3, [
	amode:		lookup(+, ++, ?),
	args:		["Tree":"A 2-3-4 tree",
			"Key":"A key to search for",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for a key.",
	fail_if:	"Fails if Value does not unify with the value corresponding to Key.",
	resat:		no,
	see_also:	[member/3, search/3],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for an entry with key Key.
	If the key is found, then it attempts to unify the corresponding
	value with Value.  If the key is not found, then it aborts with
	a runtime error.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred lower_bound_search(tree234(K, V), K, K, V).
% :- mode lower_bound_search(in, in, out, out) is semidet.
:- comment(lower_bound_search/4, [
	amode:		lower_bound_search(+, ++, ?, ?),
	args:		["Tree":"A 2-3-4 tree",
			"SearchKey":"A key to search for",
			"Key":"The key found",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for the smallest key no smaller than SearchKey.",
	fail_if:	"Fails if there are no keys at least as large as SearchKey in Tree or if Key and Value do not unify with the key and value found.",
	resat:		no,
	see_also:	[lower_bound_lookup/4,
			upper_bound_search/4,
			upper_bound_lookup/4],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for the entry with the
	smallest key which is no smaller than SearchKey.  If such a key is
	found, then it attempts to unify it with Key and the corresponding
	value with Value.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred lower_bound_lookup(tree234(K, V), K, K, V).
% :- mode lower_bound_lookup(in, in, out, out) is det.
:- comment(lower_bound_lookup/4, [
	amode:		lower_bound_lookup(+, ++, ?, ?),
	args:		["Tree":"A 2-3-4 tree",
			"SearchKey":"A key to search for",
			"Key":"The key found",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for the smallest key no smaller than SearchKey.",
	fail_if:	"Fails if Key and Value do not unify with the key and value found.",
	resat:		no,
	see_also:	[lower_bound_search/4,
			upper_bound_search/4,
			upper_bound_lookup/4],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for the entry with the
	smallest key which is no smaller than SearchKey.  If such a key is
	found, then it attempts to unify it with Key and the corresponding
	value with Value.  If such a key is not found, then it aborts with
	a runtime error.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred upper_bound_search(tree234(K, V), K, K, V).
% :- mode upper_bound_search(in, in, out, out) is semidet.
:- comment(upper_bound_search/4, [
	amode:		upper_bound_search(+, ++, ?, ?),
	args:		["Tree":"A 2-3-4 tree",
			"SearchKey":"A key to search for",
			"Key":"The key found",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for the largest key no larger than SearchKey.",
	fail_if:	"Fails if there are no keys at least as large as SearchKey in Tree or if Key and Value do not unify with the key and value found.",
	resat:		no,
	see_also:	[lower_bound_search/4,
			lower_bound_lookup/4,
			upper_bound_lookup/4],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for the entry with the
	largest key which is no larger than SearchKey.  If such a key is
	found, then it attempts to unify it with Key and the corresponding
	value with Value.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred upper_bound_lookup(tree234(K, V), K, K, V).
% :- mode upper_bound_lookup(in, in, out, out) is det.
:- comment(upper_bound_lookup/4, [
	amode:		upper_bound_lookup(+, ++, ?, ?),
	args:		["Tree":"A 2-3-4 tree",
			"SearchKey":"A key to search for",
			"Key":"The key found",
			"Value":"The value corresponding to Key"],
	summary:	"Search a tree for the largest key no larger than SearchKey.",
	fail_if:	"Fails if Key and Value do not unify with the key and value found.",
	resat:		no,
	see_also:	[lower_bound_search/4,
			lower_bound_lookup/4,
			upper_bound_search/4],
	desc:           html("\
	<P>
	This predicate searches the tree Tree for the entry with the
	largest key which is no larger than SearchKey.  If such a key is
	found, then it attempts to unify it with Key and the corresponding
	value with Value.  If such a key is not found, then it aborts with
	a runtime error.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred insert(tree234(K, V), K, V, tree234(K, V)).
% :- mode insert(in, in, in, out) is semidet.
% % :- mode insert(di_tree234, in, in, uo_tree234) is semidet.
% % :- mode insert(in, in, in, out) is semidet.
:- comment(insert/4, [
	amode:		insert(+, ++, ?, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"A key to insert",
			"Value":"The value corresponding to Key",
			"Tree":"The tree after insertion"],
	summary:	"Insert a key/value pair into a tree, failing if the key already exists.",
	fail_if:	"Fails if Key already appears in Tree0.",
	resat:		no,
	see_also:	[update/4, set/4],
	desc:           html("\
	<P>
	This predicate inserts the key Key with corresponding value Value
	into the tree Tree0, resulting in the tree Tree.  If the key Key is
	already in the tree, then the predicate fails.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred set(tree234(K, V), K, V, tree234(K, V)).
% :- mode set(di, di, di, uo) is det.
% % :- mode set(di_tree234, in, in, uo_tree234) is det.
% :- mode set(in, in, in, out) is det.
:- comment(set/4, [
	amode:		set(+, ++, ?, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"A key to set",
			"Value":"The value corresponding to Key",
			"Tree":"The tree after setting"],
	summary:	"Update the value corresponding to a key in a tree, inserting the key if it doesn't exist already.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[insert/4, update/4],
	desc:           html("\
	<P>
	If the key Key already exists in the tree Tree0, then this predicate
	updates the corresponding value to be Value.  Otherwise it inserts
	the key Key into the tree with value Value.  The resulting tree is
	Tree.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred delete(tree234(K, V), K, tree234(K, V)).
% :- mode delete(di, in, uo) is det.
% % :- mode delete(di_tree234, in, uo_tree234) is det.
% :- mode delete(in, in, out) is det.
:- comment(delete/3, [
	amode:		delete(+, ++, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"The key to delete",
			"Tree":"The tree after deletion"],
	summary:	"Delete a key/value pair from a tree.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[remove/4],
	desc:           html("\
	<P>
	If the key Key appears in the tree Tree0, then remove it and its
	corresponding value, resulting in the tree Tree.  If the key Key
	does not appear, Tree is simply bound to Tree0.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred remove(tree234(K, V), K, V, tree234(K, V)).
% :- mode remove(di, in, uo, uo) is semidet.
% % :- mode remove(di_tree234, in, out, uo_tree234) is semidet.
% :- mode remove(in, in, out, out) is semidet.
:- comment(remove/4, [
	amode:		remove(+, ++, ?, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"The key to remove",
			"Value":"The value corresponding to Key",
			"Tree":"The tree after removal"],
	summary:	"Remove a key/value pair from a tree, failing if the key is not present.",
	fail_if:	"Fails is Key does not appear in Tree0 or if Value does not unify with the corresponding value.",
	resat:		no,
	see_also:	[delete/3, remove_smallest/4],
	desc:           html("\
	<P>
	If the key Key appears in the tree Tree0, then remove it and attempt
	to unify its corresponding value with Value.  Tree is Tree0 with the
	key removed.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred remove_smallest(tree234(K, V), K, V, tree234(K, V)).
% :- mode remove_smallest(di, uo, uo, uo) is semidet.
% % :- mode remove_smallest(di_tree234, out, out, uo_tree234)
% %	is semidet.
% :- mode remove_smallest(in, out, out, out) is semidet.
:- comment(remove_smallest/4, [
	amode:		remove_smallest(+, ?, ?, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"The key removed",
			"Value":"The value corresponding to Key",
			"Tree":"The tree after removal"],
	summary:	"Remove the smallest key and its corresponding value from a tree.",
	fail_if:	"Fails if Tree0 is empty or if Key and Value do not unify with the key and value removed.",
	resat:		no,
	see_also:	[remove/4],
	desc:           html("\
	<P>
	Removes the smallest key in the tree Tree0 (resulting in the
	tree Tree), and attempts to unify the removed key with Key and 
	its corresponding value with Value.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred keys(tree234(K, V), list(K)).
% :- mode keys(in, out) is det.
:- comment(keys/2, [
	amode:		keys(+, -),
	args:		["Tree":"A 2-3-4 tree",
			"KeyList":"A list of all the keys from Tree"],
	summary:	"Return all the keys from a tree.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[values/2],
	desc:           html("\
	<P>
	KeyList is a list of all the keys appearing in the tree Tree.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred values(tree234(K, V), list(V)).
% :- mode values(in, out) is det.
:- comment(values/2, [
	amode:		values(+, -),
	args:		["Tree":"A 2-3-4 tree",
			"ValueList":"A list of all the values from Tree"],
	summary:	"Return all the values from a tree.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[values/2],
	desc:           html("\
	<P>
	ValueList is a list of all the values appearing in the tree Tree.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred update(tree234(K, V), K, V, tree234(K, V)).
% :- mode update(in, in, in, out) is semidet.
% % :- mode update(di_tree234, in, in, uo_tree234) is det.
% % :- mode update(di, di, di, uo) is semidet.
:- comment(update/4, [
	amode:		update(+, ++, ?, -),
	args:		["Tree0":"A 2-3-4 tree",
			"Key":"A key to update",
			"Value":"The value corresponding to Key",
			"Tree":"The tree after updating"],
	summary:	"Update the value corresponding to a key in a tree.",
	fail_if:	"Fails if Key does not appear in Tree0.",
	resat:		no,
	see_also:	[insert/4, set/4],
	desc:           html("\
	<P>
	If the key Key already exists in the tree Tree0, then this predicate
	updates the corresponding value to be Value.  The resulting tree is
	Tree.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% 	% count the number of elements in a tree
% :- pred count(tree234(K, V), int).
% :- mode count(in, out) is det.
:- comment(count/2, [
	amode:		count(+, ?),
	args:		["Tree":"A 2-3-4 tree",
			"Count":"The number of elements in Tree"],
	summary:	"Count the number of elements in a tree.",
	fail_if:	"Fails if Count does not unify with the number of elements in Tree.",
	see_also:	[insert/4, update/4],
	resat:		no,
	desc:           html("\
	<P>
	Counts the number of elements in the tree Tree (in linear time),
        and attempts to unify the result with Count.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).

% :- pred assoc_list_to_tree234(assoc_list(K, V), tree234(K, V)).
% :- mode assoc_list_to_tree234(in, out) is det.
:- comment(assoc_list_to_tree234/2, [
	amode:		assoc_list_to_tree234(+, -),
	args:		["AssocList":"A list of key-value pairs",
			"Tree":"A 2-3-4 tree"],
	summary:	"Converts an association list into a tree.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[tree234_to_assoc_list/2],
	desc:           html("\
	<P>
	AssocList is a list of key/value pairs of the form Key-Value,
	and Tree is a tree containing these key/value pairs.  If a key
	appears more than once in AssocList, then its corresponding value
	in the tree Tree will be the last one appearing in AssocList.
	</P>
	")
]).

% :- pred tree234_to_assoc_list(tree234(K, V), assoc_list(K, V)).
% :- mode tree234_to_assoc_list(in, out) is det.
:- comment(tree234_to_assoc_list/2, [
	amode:		tree234_to_assoc_list(+, -),
	args:		["Tree":"A 2-3-4 tree",
			"AssocList":"A list of the key-value pairs from Tree"],
	summary:	"Converts a tree into an association list.",
	fail_if:	"Never fails.",
	resat:		no,
	see_also:	[assoc_list_to_tree234/2],
	desc:           html("\
	<P>
	AssocList is a list containing the key/value pairs from the tree
	Tree, in the form Key-Value.
	</P>
	<P>
	This predicate should only be called with trees created by other
	predicates from the tree234 module.
	</P>
	")
]).


%------------------------------------------------------------------------------%
%------------------------------------------------------------------------------%

% :- import_module int, require, bool, std_util.

% :- type tree234(K, V)	--->
% 		empty
% 	;	two(K, V, tree234(K, V), tree234(K, V))
% 	;	three(K, V, K, V, tree234(K, V), tree234(K, V), tree234(K, V))
% 	;	four(K, V, K, V, K, V, tree234(K, V), tree234(K, V),
% 			tree234(K, V), tree234(K, V)).


:- lib(mercury).

%------------------------------------------------------------------------------%

init(empty).

is_empty(empty) ?- true.

is_tree234(empty) ?- true.
is_tree234(two(_,_,_,_)) ?- true.
is_tree234(three(_,_,_,_,_,_,_)) ?- true.
is_tree234(four(_,_,_,_,_,_,_,_,_,_)) ?- true.


%------------------------------------------------------------------------------%

%member(empty, _K, _V) :- fail.
member(two(K0, V0, T0, T1), K, V) ?-
	(
            K = K0, V = V0
	;
            member(T0, K, V)
	;
            member(T1, K, V)
	).
member(three(K0, V0, K1, V1, T0, T1, T2), K, V) ?-
	(
            K = K0, V = V0
	;
            K = K1, V = V1
	;
            member(T0, K, V)
	;
            member(T1, K, V)
	;
            member(T2, K, V)
	).
member(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, V) ?-
	(
            K = K0, V = V0
	;
            K = K1, V = V1
	;
            K = K2, V = V2
	;
            member(T0, K, V)
	;
            member(T1, K, V)
	;
            member(T2, K, V)
	;
            member(T3, K, V)
	).

%------------------------------------------------------------------------------%

%search(empty, _K, _V) :- fail.
search(two(K0, V0, T0, T1), K, V) ?-
	compare(Result0, K, K0),
        ( Result0 == (<), search(T0, K, V)
        ; Result0 == (=), V = V0
        ; Result0 == (>), search(T1, K, V)
        ).
search(three(K0, V0, K1, V1, T0, T1, T2), K, V) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            search(T0, K, V)
        ; Result0 == (=),
            V = V0
        ; Result0 == (>),
            compare(Result1, K, K1),
            ( Result1 == (<), search(T1, K, V)
            ; Result1 == (=), V = V1
            ; Result1 == (>), search(T2, K, V)
            )
        ).
search(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, V) ?-
	compare(Result1, K, K1),
        ( Result1 == (<),
            compare(Result0, K, K0),
            ( Result0 == (<), search(T0, K, V)
            ; Result0 == (=), V = V0
            ; Result0 == (>), search(T1, K, V)
            )
        ; Result1 == (=),
            V = V1
        ; Result1 == (>),
            compare(Result2, K, K2),
            ( Result2 == (<), search(T2, K, V)
            ; Result2 == (=), V = V2
            ; Result2 == (>), search(T3, K, V)
            )
        ).


lookup(T, K, V) :-
	( search(T, K, V0) ->
		V = V0
	;
		report_lookup_error("lookup: key not found.", K, V)
	).

%------------------------------------------------------------------------------%

%lower_bound_search(empty, _SearchK, _K, _V) ?-
%	fail.
lower_bound_search(two(K0, V0, T0, T1), SearchK, K, V) ?-
	compare(Result, SearchK, K0),
        ( Result == (<),
            lower_bound_search(T0, SearchK, K, V)
        ; Result == (=),
            K = SearchK, V = V0
        ; Result == (>),
            ( lower_bound_search(T1, SearchK, Kp, Vp) ->
                K = Kp, V = Vp
            ;
                K = K0, V = V0
            )
        ).
lower_bound_search(three(K0, V0, K1, V1, T0, T1, T2), SearchK, K, V) ?-
	compare(Result0, SearchK, K0),
        ( Result0 == (<),
            lower_bound_search(T0, SearchK, K, V)
        ; Result0 == (=),
            K = SearchK, V = V0
        ; Result0 == (>),
            compare(Result1, SearchK, K1),
            ( Result1 == (<),
                ( lower_bound_search(T1, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K0, V = V0
                )
            ; Result1 == (=),
                K = SearchK, V = V1
            ; Result1 == (>),
                ( lower_bound_search(T2, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K1, V = V1
                )
            )
        ).
lower_bound_search(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), SearchK, K, V) ?-
	compare(Result1, SearchK, K1),
        ( Result1 == (<),
            compare(Result0, SearchK, K0),
            ( Result0 == (<),
                lower_bound_search(T0, SearchK, K, V)
            ; Result0 == (=),
                K = SearchK, V = V0
            ; Result0 == (>),
                ( lower_bound_search(T1, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K0, V = V0
                )
            )
        ; Result1 == (=),
            K = SearchK, V = V1
        ; Result1 == (>),
            compare(Result2, SearchK, K2),
            ( Result2 == (<),
                ( lower_bound_search(T2, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K1, V = V1
                )
            ; Result2 == (=),
                K = SearchK, V = V2
            ; Result2 == (>),
                ( lower_bound_search(T3, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K2, V = V2
                )
            )
        ).


lower_bound_lookup(T, SearchK, K, V) :-
	( lower_bound_search(T, SearchK, K0, V0) ->
            K = K0, V = V0
	;
            report_lookup_error("lower_bound_lookup: key not found.", SearchK, V)
	).

%------------------------------------------------------------------------------%

%upper_bound_search(empty, _SearchK, _K, _V) ?-
%	fail.
upper_bound_search(two(K0, V0, T0, T1), SearchK, K, V) ?-
	compare(Result, SearchK, K0),
        ( Result == (<),
            ( upper_bound_search(T0, SearchK, Kp, Vp) ->
                K = Kp, V = Vp
            ;
                K = K0, V = V0
            )
        ; Result == (=),
            K = SearchK, V = V0
        ; Result == (>),
            upper_bound_search(T1, SearchK, K, V)
        ).
upper_bound_search(three(K0, V0, K1, V1, T0, T1, T2), SearchK, K, V) ?-
	compare(Result0, SearchK, K0),
        ( Result0 == (<),
            ( upper_bound_search(T0, SearchK, Kp, Vp) ->
                K = Kp, V = Vp
            ;
                K = K0, V = V0
            )
        ; Result0 == (=),
            K = SearchK, V = V0
        ; Result0 == (>),
            compare(Result1, SearchK, K1),
            ( Result1 == (<),
                ( upper_bound_search(T1, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K1, V = V1
                )
            ; Result1 == (=),
                K = SearchK, V = V1
            ; Result1 == (>),
                upper_bound_search(T2, SearchK, K, V)
            )
        ).
upper_bound_search(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), SearchK, K, V) ?-
	compare(Result1, SearchK, K1),
        ( Result1 == (<),
            compare(Result0, SearchK, K0),
            ( Result0 == (<),
                ( upper_bound_search(T0, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K0, V = V0
                )
            ; Result0 == (=),
                K = SearchK, V = V0
            ; Result0 == (>),
                ( upper_bound_search(T1, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K1, V = V1
                )
            )
        ; Result1 == (=),
            K = SearchK, V = V1
        ; Result1 == (>),
            compare(Result2, SearchK, K2),
            ( Result2 == (<),
                ( upper_bound_search(T2, SearchK, Kp, Vp) ->
                    K = Kp, V = Vp
                ;
                    K = K2, V = V2
                )
            ; Result2 == (=),
                K = SearchK, V = V2
            ; Result2 == (>),
                upper_bound_search(T3, SearchK, K, V)
            )
        ).


upper_bound_lookup(T, SearchK, K, V) :-
	( upper_bound_search(T, SearchK, K0, V0) ->
            K = K0, V = V0
	;
            report_lookup_error("upper_bound_lookup: key not found.", SearchK, V)
	).

%------------------------------------------------------------------------------%

%update(empty, _K, _V, _Tout) ?-
%	fail.
update(two(K0, V0, T0, T1), K, V, Tout) ?-
	compare(Result, K, K0),
        ( Result == (<),
            Tout = two(K0, V0, NewT0, T1),
            update(T0, K, V, NewT0)
        ; Result == (=),
            Tout = two(K0, V, T0, T1)
        ; Result == (>),
            Tout = two(K0, V0, T0, NewT1),
            update(T1, K, V, NewT1)
        ).
update(three(K0, V0, K1, V1, T0, T1, T2), K, V, Tout) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
            update(T0, K, V, NewT0)
        ; Result0 == (=),
            Tout = three(K0, V, K1, V1, T0, T1, T2)
        ; Result0 == (>),
            compare(Result1, K, K1),
            ( Result1 == (<),
                Tout = three(K0, V0, K1, V1, T0, NewT1, T2),
                update(T1, K, V, NewT1)
            ; Result1 == (=),
                Tout = three(K0, V0, K1, V, T0, T1, T2)
            ; Result1 == (>),
                Tout = three(K0, V0, K1, V1, T0, T1, NewT2),
                update(T2, K, V, NewT2)
            )
        ).
update(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, V, Tout) ?-
	compare(Result1, K, K1),
        ( Result1 == (<),
            compare(Result0, K, K0),
            ( Result0 == (<),
                Tout = four(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3),
                update(T0, K, V, NewT0)
            ; Result0 == (=),
                Tout = four(K0, V, K1, V1, K2, V2, T0, T1, T2, T3)
            ; Result0 == (>),
                Tout = four(K0, V0, K1, V1, K2, V2, T0, NewT1, T2, T3),
                update(T1, K, V, NewT1)
            )
        ; Result1 == (=),
            Tout = four(K0, V0, K1, V, K2, V2, T0, T1, T2, T3)
        ; Result1 == (>),
            compare(Result2, K, K2),
            ( Result2 == (<),
                Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, NewT2, T3),
                update(T2, K, V, NewT2)
            ; Result2 == (=),
                Tout = four(K0, V0, K1, V1, K2, V, T0, T1, T2, T3)
            ; Result2 == (>),
                Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, NewT3),
                update(T3, K, V, NewT3)
            )
        ).


%------------------------------------------------------------------------------%
%------------------------------------------------------------------------------%

% :- inst two(K, V, T) =
% 	bound(
% 		two(K, V, T, T)
% 	).
% 
% :- inst uniq_two(K, V, T) =
% 	unique(
% 		two(K, V, T, T)
% 	).
% 
% :- inst three(K, V, T) =
% 	bound(
% 		three(K, V, K, V, T, T, T)
% 	).
% 
% :- inst uniq_three(K, V, T) =
% 	unique(
% 		three(K, V, K, V, T, T, T)
% 	).
% 
% :- inst four(K, V, T) =
% 	bound(
% 		four(K, V, K, V, K, V, T, T, T, T)
% 	).
% 
% :- inst uniq_four(K, V, T) =
% 	unique(
% 		four(K, V, K, V, K, V, T, T, T, T)
% 	).
% 
% :- mode uo_two :: out(uniq_two(unique, unique, unique)).
% :- mode suo_two :: out(uniq_two(ground, ground, uniq_tree234_gg)).
% :- mode out_two :: out(two(ground, ground, ground)).
% 
% :- mode di_two :: di(uniq_two(unique, unique, unique)).
% :- mode sdi_two :: di(uniq_two(ground, ground, uniq_tree234_gg)).
% :- mode in_two :: in(two(ground, ground, ground)).
% 
% :- mode di_three :: di(uniq_three(unique, unique, unique)).
% :- mode sdi_three :: di(uniq_three(ground, ground, uniq_tree234_gg)).
% :- mode in_three :: in(three(ground, ground, ground)).
% 
% :- mode di_four :: di(uniq_four(unique, unique, unique)).
% :- mode sdi_four :: di(uniq_four(ground, ground, uniq_tree234_gg)).
% :- mode in_four :: in(four(ground, ground, ground)).

%------------------------------------------------------------------------------%

% :- pred split_four(tree234(K, V), K, V, tree234(K, V), tree234(K, V)).
% :- mode split_four(di_four, uo, uo, uo_two, uo_two) is det.
% % :- mode split_four(sdi_four, out, out, suo_two, suo_two) is det.
% :- mode split_four(in_four, out, out, out_two, out_two) is det.

split_four(four(K0, V0, MidK, MidV, K2, V2, T0, T1, T2, T3),
           MidK, MidV,
           two(K0, V0, T0, T1), two(K2, V2, T2, T3)
       ).

%------------------------------------------------------------------------------%

% insert is implemented using the simple top-down
% approach described in eg Sedgwick which splits 4 nodes into
% two 2 nodes on the downward traversal of the tree as we
% search for the right place to insert the new key-value pair.
% We know we have the right place if the subtrees of the node
% are empty (in which case we expand the node - which will always
% work because we already split 4 nodes into 2 nodes), or if the
% tree itself is empty.
% This algorithm is O(lgN).
%
% set uses the same algorithm as used for insert,
% except that instead of failing for equal keys, we replace the value.

insert(Tin, K, V, Tout) :-
        set(Tin, K, V, Tout, true).

set(Tin, K, V, Tout) :-
        set(Tin, K, V, Tout, false).


set(Tin, _K, _V, _Tout, _Insert) :- var(Tin), !, fail.
set(Tin, K, V, Tout, _Insert) :-
	Tin == empty,
	Tout = two(K, V, empty, empty).
set(Tin, K, V, Tout, Insert) :-
	Tin = two(_, _, _, _),
	set2(Tin, K, V, Tout, Insert).
set(Tin, K, V, Tout, Insert) :-
	Tin = three(_, _, _, _, _, _, _),
	set3(Tin, K, V, Tout, Insert).
set(Tin, K, V, Tout, Insert) :-
	Tin = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
	compare(Result1, K, K1),
        ( Result1 == (<),
            Sub0 = two(K0, V0, T0, T1),
            Sub1 = two(K2, V2, T2, T3),
            Tout = two(K1, V1, NewSub0, Sub1),
            set2(Sub0, K, V, NewSub0, Insert)
        ; Result1 == (=),
            Insert == false,
            Tout = four(K0, V0, K1, V, K2, V2, T0, T1, T2, T3)
        ; Result1 == (>),
            Sub0 = two(K0, V0, T0, T1),
            Sub1 = two(K2, V2, T2, T3),
            Tout = two(K1, V1, Sub0, NewSub1),
            set2(Sub1, K, V, NewSub1, Insert)
        ).

% :- pred set2(tree234(K, V), K, V, tree234(K, V)).
% :- mode set2(di_two, di, di, uo) is det.
% % :- mode set2(sdi_two, in, in, uo_tree234) is det.
% :- mode set2(in_two, in, in, out) is det.

set2(two(K0, V0, T0, T1), K, V, Tout, Insert) :-
	( T0 == empty, T1 == empty ->
            compare(Result, K, K0),
            ( Result == (<),
                Tout = three(K, V, K0, V0, empty, empty, empty)
            ; Result == (=),
                Insert == false,
                Tout = two(K, V, T0, T1)
            ; Result == (>),
                Tout = three(K0, V0, K, V, empty, empty, empty)
            )
	;
            compare(Result, K, K0),
            ( Result == (<),
                ( T0 = empty,
                    NewT0 = two(K, V, empty, empty),
                    Tout = two(K0, V0, NewT0, T1)
                ; T0 = two(_, _, _, _),
                    Tout = two(K0, V0, NewT0, T1),
                    set2(T0, K, V, NewT0, Insert)
                ; T0 = three(_, _, _, _, _, _, _),
                    Tout = two(K0, V0, NewT0, T1),
                    set3(T0, K, V, NewT0, Insert)
                ; T0 = four(_, _, _, _, _, _, _, _, _, _),
                    split_four(T0, MT0K, MT0V, T00, T01),
                    compare(Result1, K, MT0K),
                    ( Result1 == (<),
                        Tout = three(MT0K, MT0V, K0, V0, NewT00, T01, T1),
                        set2(T00, K, V, NewT00, Insert)
                    ; Result1 == (=),
                        Insert == false,
                        Tout = three(MT0K, V, K0, V0, T00, T01, T1)
                    ; Result1 == (>),
                        Tout = three(MT0K, MT0V, K0, V0, T00, NewT01, T1),
                        set2(T01, K, V, NewT01, Insert)
                    )
                )
            ; Result == (=),
                Insert == false,
                Tout = two(K, V, T0, T1)
            ; Result == (>),
                ( T1 = empty,
                    NewT1 = two(K, V, empty, empty),
                    Tout = two(K0, V0, T0, NewT1)
                ; T1 = two(_, _, _, _),
                    Tout = two(K0, V0, T0, NewT1),
                    set2(T1, K, V, NewT1, Insert)
                ; T1 = three(_, _, _, _, _, _, _),
                    Tout = two(K0, V0, T0, NewT1),
                    set3(T1, K, V, NewT1, Insert)
                ; T1 = four(_, _, _, _, _, _, _, _, _, _),
                    split_four(T1, MT1K, MT1V, T10, T11),
                    compare(Result1, K, MT1K),
                    ( Result1 == (<),
                        Tout = three(K0, V0, MT1K, MT1V, T0, NewT10, T11),
                        set2(T10, K, V, NewT10, Insert)
                    ; Result1 == (=),
                        Insert == false,
                        Tout = three(K0, V0, MT1K, V, T0, T10, T11)
                    ; Result1 == (>),
                        Tout = three(K0, V0, MT1K, MT1V, T0, T10, NewT11),
                        set2(T11, K, V, NewT11, Insert)
                    )
                )
            )
	).


% :- pred set3(tree234(K, V), K, V, tree234(K, V)).
% :- mode set3(di_three, di, di, uo) is det.
% % :- mode set3(sdi_three, in, in, uo_tree234) is det.
% :- mode set3(in_three, in, in, out) is det.

set3(three(K0, V0, K1, V1, T0, T1, T2), K, V, Tout, Insert) :-
	( T0 == empty, T1 == empty, T2 == empty ->
            compare(Result0, K, K0),
            ( Result0 == (<),
                Tout = four(K, V, K0, V0, K1, V1, empty, empty, empty, empty)
            ; Result0 == (=),
                Insert == false,
                Tout = three(K0, V, K1, V1, empty, empty, empty)
            ; Result0 == (>),
                compare(Result1, K, K1),
                ( Result1 == (<),
                    Tout = four(K0, V0, K, V, K1, V1, empty, empty, empty, empty)
                ; Result1 == (=),
                    Insert == false,
                    Tout = three(K0, V0, K1, V, empty, empty, empty)
                ; Result1 == (>),
                    Tout = four(K0, V0, K1, V1, K, V, empty, empty, empty, empty)
                )
            )
	;
            compare(Result0, K, K0),
            ( Result0 == (<),
                ( T0 = empty,
                    NewT0 = two(K, V, empty, empty),
                    Tout = three(K0, V0, K1, V1, NewT0, T1, T2)
                ; T0 = two(_, _, _, _),
                    Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
                    set2(T0, K, V, NewT0, Insert)
                ; T0 = three(_, _, _, _, _, _, _),
                    Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
                    set3(T0, K, V, NewT0, Insert)
                ; T0 = four(_, _, _, _, _, _, _, _, _, _),
                    split_four(T0, MT0K, MT0V, T00, T01),
                    compare(ResultM, K, MT0K),
                    ( ResultM == (<),
                        Tout = four(MT0K, MT0V, K0, V0, K1, V1, NewT00, T01, T1, T2),
                        set2(T00, K, V, NewT00, Insert)
                    ; ResultM == (=),
                        Insert == false,
                        Tout = four(MT0K, V, K0, V0, K1, V1, T00, T01, T1, T2)
                    ; ResultM == (>),
                        Tout = four(MT0K, MT0V, K0, V0, K1, V1, T00, NewT01, T1, T2),
                        set2(T01, K, V, NewT01, Insert)
                    )
                )
            ; Result0 == (=),
                Insert == false,
                Tout = three(K0, V, K1, V1, T0, T1, T2)
            ; Result0 == (>),
                compare(Result1, K, K1),
                ( Result1 == (<),
                    ( T1 = empty,
                        NewT1 = two(K, V, empty, empty),
                        Tout = three(K0, V0, K1, V1, T0, NewT1, T2)
                    ; T1 = two(_, _, _, _),
                        Tout = three(K0, V0, K1, V1, T0, NewT1, T2),
                        set2(T1, K, V, NewT1, Insert)
                    ; T1 = three(_, _, _, _, _, _, _),
                        Tout = three(K0, V0, K1, V1, T0, NewT1, T2),
                        set3(T1, K, V, NewT1, Insert)
                    ; T1 = four(_, _, _, _, _, _, _, _, _, _),
                        split_four(T1, MT1K, MT1V, T10, T11),
                        compare(ResultM, K, MT1K),
                        ( ResultM == (<),
                            Tout = four(K0, V0, MT1K, MT1V, K1, V1, T0, NewT10, T11, T2),
                            set2(T10, K, V, NewT10, Insert)
                        ; ResultM == (=),
                            Insert == false,
                            Tout = four(K0, V0, MT1K, V, K1, V1, T0, T10, T11, T2)
                        ; ResultM == (>),
                            Tout = four(K0, V0, MT1K, MT1V, K1, V1, T0, T10, NewT11, T2),
                            set2(T11, K, V, NewT11, Insert)
                        )
                    )
                ; Result1 == (=),
                    Insert == false,
                    Tout = three(K0, V0, K, V, T0, T1, T2)
                ; Result1 == (>),
                    ( T2 = empty,
                        NewT2 = two(K, V, empty, empty),
                        Tout = three(K0, V0, K1, V1, T0, T1, NewT2)
                    ; T2 = two(_, _, _, _),
                        Tout = three(K0, V0, K1, V1, T0, T1, NewT2),
                        set2(T2, K, V, NewT2, Insert)
                    ; T2 = three(_, _, _, _, _, _, _),
                        Tout = three(K0, V0, K1, V1, T0, T1, NewT2),
                        set3(T2, K, V, NewT2, Insert)
                    ; T2 = four(_, _, _, _, _, _, _, _, _, _),
                        split_four(T2, MT2K, MT2V, T20, T21),
                        compare(ResultM, K, MT2K),
                        ( ResultM == (<),
                            Tout = four(K0, V0, K1, V1, MT2K, MT2V, T0, T1, NewT20, T21),
                            set2(T20, K, V, NewT20, Insert)
                        ; ResultM == (=),
                            Insert == false,
                            Tout = four(K0, V0, K1, V1, MT2K, V, T0, T1, T20, T21)
                        ; ResultM == (>),
                            Tout = four(K0, V0, K1, V1, MT2K, MT2V, T0, T1, T20, NewT21),
                            set2(T21, K, V, NewT21, Insert)
                        )
                    )
                )
            )
	).


%------------------------------------------------------------------------------%

delete(Tin, K, Tout) :-
	delete_2(Tin, K, Tout, _ReducedHeight).

	% When deleting an item from a tree, the height of the tree may be
	% reduced by one. The last argument says whether this has occurred.

% :- pred delete_2(tree234(K, V), K, tree234(K, V), bool).
% :- mode delete_2(di, in, uo, out) is det.
% :- mode delete_2(in, in, out, out) is det.

delete_2(empty, _K, Tout, RH) ?-
	Tout = empty,
	RH = no.
delete_2(two(K0, V0, T0, T1), K, Tout, RH) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            delete_2(T0, K, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_2node_t0(K0, V0, NewT0, T1, Tout, RH)
            ;
                Tout = two(K0, V0, NewT0, T1),
                RH = no
            )
        ; Result0 == (=),
            ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                ( RHT1 == yes ->
                    fix_2node_t1(ST1K, ST1V, T0, NewT1, Tout, RH)
                ;
                    Tout = two(ST1K, ST1V, T0, NewT1),
                    RH = no
                )
            ;
                Tout = T0,
                RH = yes
            )
        ; Result0 == (>),
            delete_2(T1, K, NewT1, RHT1),
            ( RHT1 == yes ->
                fix_2node_t1(K0, V0, T0, NewT1, Tout, RH)
            ;
                Tout = two(K0, V0, T0, NewT1),
                RH = no
            )
        ).
delete_2(three(K0, V0, K1, V1, T0, T1, T2), K, Tout, RH) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            delete_2(T0, K, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_3node_t0(K0, V0, K1, V1, NewT0, T1, T2, Tout, RH)
            ;
                Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
                RH = no
            )
        ; Result0 == (=),
            ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                ( RHT1 == yes ->
                    fix_3node_t1(ST1K, ST1V, K1, V1, T0, NewT1, T2, Tout, RH)
                ;
                    Tout = three(ST1K, ST1V, K1, V1, T0, NewT1, T2),
                    RH = no
                )
            ;
                Tout = two(K1, V1, T0, T2),
                RH = no
            )
        ; Result0 == (>),
            compare(Result1, K, K1),
            ( Result1 == (<),
                delete_2(T1, K, NewT1, RHT1),
                ( RHT1 == yes ->
                    fix_3node_t1(K0, V0, K1, V1, T0, NewT1, T2, Tout, RH)
                ;
                    Tout = three(K0, V0, K1, V1, T0, NewT1, T2),
                    RH = no
                )
            ; Result1 == (=),
                ( remove_smallest_2(T2, ST2K, ST2V, NewT2, RHT2) ->
                    ( RHT2 == yes ->
                        fix_3node_t2(K0, V0, ST2K, ST2V, T0, T1, NewT2, Tout, RH)
                    ;
                        Tout = three(K0, V0, ST2K, ST2V, T0, T1, NewT2),
                        RH = no
                    )
                ;
                    Tout = two(K0, V0, T0, T1),
                    RH = no
                )
            ; Result1 == (>),
                delete_2(T2, K, NewT2, RHT2),
                ( RHT2 == yes ->
                    fix_3node_t2(K0, V0, K1, V1, T0, T1, NewT2, Tout, RH)
                ;
                    Tout = three(K0, V0, K1, V1, T0, T1, NewT2),
                    RH = no
                )
            )
        ).
delete_2(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, Tout, RH) ?-
	compare(Result1, K, K1),
        ( Result1 == (<),
            compare(Result0, K, K0),
            ( Result0 == (<),
                delete_2(T0, K, NewT0, RHT0),
                ( RHT0 == yes ->
                    fix_4node_t0(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3),
                    RH = no
                )
            ; Result0 == (=),
                ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                    ( RHT1 == yes ->
                        fix_4node_t1(ST1K, ST1V, K1, V1, K2, V2, T0, NewT1, T2, T3, Tout, RH)
                    ;
                        Tout = four(ST1K, ST1V, K1, V1, K2, V2, T0, NewT1, T2, T3),
                        RH = no
                    )
                ;
                    Tout = three(K1, V1, K2, V2, T0, T2, T3),
                    RH = no
                )
            ; Result0 == (>),
                delete_2(T1, K, NewT1, RHT1),
                ( RHT1 == yes ->
                    fix_4node_t1(K0, V0, K1, V1, K2, V2, T0, NewT1, T2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, NewT1, T2, T3),
                    RH = no
                )
            )
        ; Result1 == (=),
            ( remove_smallest_2(T2, ST2K, ST2V, NewT2, RHT2) ->
                ( RHT2 == yes ->
                    fix_4node_t2(K0, V0, ST2K, ST2V, K2, V2, T0, T1, NewT2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, ST2K, ST2V, K2, V2, T0, T1, NewT2, T3),
                    RH = no
                )
            ;
                Tout = three(K0, V0, K2, V2, T0, T1, T3),
                RH = no
            )
        ; Result1 == (>),
            compare(Result2, K, K2),
            ( Result2 == (<),
                delete_2(T2, K, NewT2, RHT2),
                ( RHT2 == yes ->
                    fix_4node_t2(K0, V0, K1, V1, K2, V2, T0, T1, NewT2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, NewT2, T3),
                    RH = no
                )
            ; Result2 == (=),
                ( remove_smallest_2(T3, ST3K, ST3V, NewT3, RHT3) ->
                    ( RHT3 == yes ->
                        fix_4node_t3(K0, V0, K1, V1, ST3K, ST3V, T0, T1, T2, NewT3, Tout, RH)
                    ;
                        Tout = four(K0, V0, K1, V1, ST3K, ST3V, T0, T1, T2, NewT3),
                        RH = no
                    )
                ;
                    Tout = three(K0, V0, K1, V1, T0, T1, T2),
                    RH = no
                )
            ; Result2 == (>),
                delete_2(T3, K, NewT3, RHT3),
                ( RHT3 == yes ->
                    fix_4node_t3(K0, V0, K1, V1, K2, V2, T0, T1, T2, NewT3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, NewT3),
                    RH = no
                )
            )
        ).



%------------------------------------------------------------------------------%

	% We use the same algorithm as delete.

remove(Tin, K, V, Tout) :-
	remove_2(Tin, K, V, Tout, _ReducedHeight).

% :- pred remove_2(tree234(K, V), K, V, tree234(K, V), bool).
% :- mode remove_2(di, in, uo, uo, out) is semidet.
% :- mode remove_2(in, in, out, out, out) is semidet.

%remove_2(empty, _K, _V, _Tout, _RH) ?-
%	fail.
remove_2(two(K0, V0, T0, T1), K, V, Tout, RH) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            remove_2(T0, K, V, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_2node_t0(K0, V0, NewT0, T1, Tout, RH)
            ;
                Tout = two(K0, V0, NewT0, T1),
                RH = no
            )
        ; Result0 == (=),
            ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                ( RHT1 == yes ->
                    fix_2node_t1(ST1K, ST1V, T0, NewT1, Tout, RH)
                ;
                    Tout = two(ST1K, ST1V, T0, NewT1),
                    RH = no
                )
            ;
                Tout = T0,
                RH = yes
            ),
            V = V0
        ; Result0 == (>),
            remove_2(T1, K, V, NewT1, RHT1),
            ( RHT1 == yes ->
                fix_2node_t1(K0, V0, T0, NewT1, Tout, RH)
            ;
                Tout = two(K0, V0, T0, NewT1),
                RH = no
            )
        ).
remove_2(three(K0, V0, K1, V1, T0, T1, T2), K, V, Tout, RH) ?-
	compare(Result0, K, K0),
        ( Result0 == (<),
            remove_2(T0, K, V, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_3node_t0(K0, V0, K1, V1, NewT0, T1, T2, Tout, RH)
            ;
                Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
                RH = no
            )
        ; Result0 == (=),
            ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                ( RHT1 == yes ->
                    fix_3node_t1(ST1K, ST1V, K1, V1, T0, NewT1, T2, Tout, RH)
                ;
                    Tout = three(ST1K, ST1V, K1, V1, T0, NewT1, T2),
                    RH = no
                )
            ;
                Tout = two(K1, V1, T0, T2),
                RH = no
            ),
            V = V0
        ; Result0 == (>),
            compare(Result1, K, K1),
            ( Result1 == (<),
                remove_2(T1, K, V, NewT1, RHT1),
                ( RHT1 == yes ->
                    fix_3node_t1(K0, V0, K1, V1, T0, NewT1, T2, Tout, RH)
                ;
                    Tout = three(K0, V0, K1, V1, T0, NewT1, T2),
                    RH = no
                )
            ; Result1 == (=),
                ( remove_smallest_2(T2, ST2K, ST2V, NewT2, RHT2) ->
                    ( RHT2 == yes ->
                        fix_3node_t2(K0, V0, ST2K, ST2V, T0, T1, NewT2, Tout, RH)
                    ;
                        Tout = three(K0, V0, ST2K, ST2V, T0, T1, NewT2),
                        RH = no
                    )
                ;
                    Tout = two(K0, V0, T0, T1),
                    RH = no
                ),
                V = V1
            ; Result1 == (>),
                remove_2(T2, K, V, NewT2, RHT2),
                ( RHT2 == yes ->
                    fix_3node_t2(K0, V0, K1, V1, T0, T1, NewT2, Tout, RH)
                ;
                    Tout = three(K0, V0, K1, V1, T0, T1, NewT2),
                    RH = no
                )
            )
        ).
remove_2(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, V, Tout, RH) ?-
	compare(Result1, K, K1),
        ( Result1 == (<),
            compare(Result0, K, K0),
            ( Result0 == (<),
                remove_2(T0, K, V, NewT0, RHT0),
                ( RHT0 == yes ->
                    fix_4node_t0(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3),
                    RH = no
                )
            ; Result0 == (=),
                ( remove_smallest_2(T1, ST1K, ST1V, NewT1, RHT1) ->
                    ( RHT1 == yes ->
                        fix_4node_t1(ST1K, ST1V, K1, V1, K2, V2, T0, NewT1, T2, T3, Tout, RH)
                    ;
                        Tout = four(ST1K, ST1V, K1, V1, K2, V2, T0, NewT1, T2, T3),
                        RH = no
                    )
                ;
                    Tout = three(K1, V1, K2, V2, T0, T2, T3),
                    RH = no
                ),
                V = V0
            ; Result0 == (>),
                remove_2(T1, K, V, NewT1, RHT1),
                ( RHT1 == yes ->
                    fix_4node_t1(K0, V0, K1, V1, K2, V2, T0, NewT1, T2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, NewT1, T2, T3),
                    RH = no
                )
            )
        ; Result1 == (=),
            ( remove_smallest_2(T2, ST2K, ST2V, NewT2, RHT2) ->
                ( RHT2 == yes ->
                    fix_4node_t2(K0, V0, ST2K, ST2V, K2, V2, T0, T1, NewT2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, ST2K, ST2V, K2, V2, T0, T1, NewT2, T3),
                    RH = no
                )
            ;
                Tout = three(K0, V0, K2, V2, T0, T1, T3),
                RH = no
            ),
            V = V1
        ; Result1 == (>),
            compare(Result2, K, K2),
            ( Result2 == (<),
                remove_2(T2, K, V, NewT2, RHT2),
                ( RHT2 == yes ->
                    fix_4node_t2(K0, V0, K1, V1, K2, V2, T0, T1, NewT2, T3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, NewT2, T3),
                    RH = no
                )
            ; Result2 == (=),
                ( remove_smallest_2(T3, ST3K, ST3V, NewT3, RHT3) ->
                    ( RHT3 == yes ->
                        fix_4node_t3(K0, V0, K1, V1, ST3K, ST3V, T0, T1, T2, NewT3, Tout, RH)
                    ;
                        Tout = four(K0, V0, K1, V1, ST3K, ST3V, T0, T1, T2, NewT3),
                        RH = no
                    )
                ;
                    Tout = three(K0, V0, K1, V1, T0, T1, T2),
                    RH = no
                ),
                V = V2
            ; Result2 == (>),
                remove_2(T3, K, V, NewT3, RHT3),
                ( RHT3 == yes ->
                    fix_4node_t3(K0, V0, K1, V1, K2, V2, T0, T1, T2, NewT3, Tout, RH)
                ;
                    Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, NewT3),
                    RH = no
                )
            )
        ).



%------------------------------------------------------------------------------%

	% The algorithm we use similar to delete, except that we
	% always go down the left subtree.

remove_smallest(Tin, K, V, Tout) :-
	remove_smallest_2(Tin, K, V, Tout, _ReducedHeight).

% :- pred remove_smallest_2(tree234(K, V), K, V, tree234(K, V), bool).
% :- mode remove_smallest_2(di, uo, uo, uo, out) is semidet.
% :- mode remove_smallest_2(in, out, out, out, out) is semidet.

%remove_smallest_2(empty, _K, _V, _Tout, _RH) ?-
%	fail.
remove_smallest_2(two(K0, V0, T0, T1), K, V, Tout, RH) ?-
	( T0 == empty ->
            K = K0,
            V = V0,
            Tout = T1,
            RH = yes
	;
            remove_smallest_2(T0, K, V, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_2node_t0(K0, V0, NewT0, T1, Tout, RH)
            ;
                Tout = two(K0, V0, NewT0, T1),
                RH = no
            )
	).
remove_smallest_2(three(K0, V0, K1, V1, T0, T1, T2), K, V, Tout, RH) ?-
	( T0 == empty ->
            K = K0,
            V = V0,
            Tout = two(K1, V1, T1, T2),
            RH = no
	;
            remove_smallest_2(T0, K, V, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_3node_t0(K0, V0, K1, V1, NewT0, T1, T2, Tout, RH)
            ;
                Tout = three(K0, V0, K1, V1, NewT0, T1, T2),
                RH = no
            )
	).
remove_smallest_2(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3), K, V, Tout, RH) ?-
	( T0 == empty ->
            K = K0,
            V = V0,
            Tout = three(K1, V1, K2, V2, T1, T2, T3),
            RH = no
	;
            remove_smallest_2(T0, K, V, NewT0, RHT0),
            ( RHT0 == yes ->
                fix_4node_t0(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3, Tout, RH)
            ;
                Tout = four(K0, V0, K1, V1, K2, V2, NewT0, T1, T2, T3),
                RH = no
            )
	).

%------------------------------------------------------------------------------%

	% The input to the following group of predicates are the components
	% of a two-, three- or four-node in which the height of the indicated
	% subtree is one less that it should be. If it is possible to increase
	% the height of that subtree by moving into it elements from its
	% neighboring subtrees, do so, and return the resulting tree with RH
	% set to no. Otherwise, return a balanced tree whose height is reduced
	% by one, with RH set to yes to indicate the reduced height.

% :- pred fix_2node_t0(K, V, tree234(K, V), tree234(K, V), tree234(K, V), bool).
% :- mode fix_2node_t0(di, di, di, di, uo, out) is det.
% :- mode fix_2node_t0(in, in, in, in, out, out) is det.

fix_2node_t0(K0, V0, T0, four(K10, V10, K11, V11, K12, V12, T10, T11, T12, T13), Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = two(K10, V10, two(K0, V0, T0, T10), three(K11, V11, K12, V12, T11, T12, T13)),
	RH = no.
fix_2node_t0(K0, V0, T0, three(K10, V10, K11, V11, T10, T11, T12), Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = two(K10, V10, two(K0, V0, T0, T10), two(K11, V11, T11, T12)),
	RH = no.
fix_2node_t0(K0, V0, T0, two(K10, V10, T10, T11), Tout, RH) ?-
	% move T0 one level down and combine it with the subtrees of T1
	% this reduces the depth of the tree
	Tout = three(K0, V0, K10, V10, T0, T10, T11),
	RH = yes.
fix_2node_t0(_K0, _V0, _T0, empty, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = two(K0, V0, T0, T1),
	% RH = yes

% :- pred fix_2node_t1(K, V, tree234(K, V), tree234(K, V), tree234(K, V), bool).
% :- mode fix_2node_t1(di, di, di, di, uo, out) is det.
% :- mode fix_2node_t1(in, in, in, in, out, out) is det.

fix_2node_t1(K0, V0, four(K00, V00, K01, V01, K02, V02, T00, T01, T02, T03), T1, Tout, RH) ?-
	% steal T0's leftmost subtree and combine it with T1
	Tout = two(K02, V02, three(K00, V00, K01, V01, T00, T01, T02), two(K0, V0, T03, T1)),
	RH = no.
fix_2node_t1(K0, V0, three(K00, V00, K01, V01, T00, T01, T02), T1, Tout, RH) ?-
	% steal T0's leftmost subtree and combine it with T1
	Tout = two(K01, V01, two(K00, V00, T00, T01), two(K0, V0, T02, T1)),
	RH = no.
fix_2node_t1(K0, V0, two(K00, V00, T00, T01), T1, Tout, RH) ?-
	% move T1 one level down and combine it with the subtrees of T0
	% this reduces the depth of the tree
	Tout = three(K00, V00, K0, V0, T00, T01, T1),
	RH = yes.
fix_2node_t1(_K0, _V0, empty, _T1, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = two(K0, V0, T0, T1),
	% RH = yes

% :- pred fix_3node_t0(K, V, K, V, tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_3node_t0(di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_3node_t0(in, in, in, in, in, in, in, out, out) is det.

fix_3node_t0(K0, V0, K1, V1, T0, four(K10, V10, K11, V11, K12, V12, T10, T11, T12, T13), T2, Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = three(K10, V10, K1, V1, two(K0, V0, T0, T10), three(K11, V11, K12, V12, T11, T12, T13), T2),
	RH = no.
fix_3node_t0(K0, V0, K1, V1, T0, three(K10, V10, K11, V11, T10, T11, T12), T2, Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = three(K10, V10, K1, V1, two(K0, V0, T0, T10), two(K11, V11, T11, T12), T2),
	RH = no.
fix_3node_t0(K0, V0, K1, V1, T0, two(K10, V10, T10, T11), T2, Tout, RH) ?-
	% move T0 one level down to become the leftmost subtree of T1
	Tout = two(K1, V1, three(K0, V0, K10, V10, T0, T10, T11), T2),
	RH = no.
fix_3node_t0(_K0, _V0, _K1, _V1, _T0, empty, _T2, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = three(K0, V0, K1, V1, T0, T1, T2),
	% The heights of T1 and T2 are unchanged
	% RH = no

% :- pred fix_3node_t1(K, V, K, V, tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_3node_t1(di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_3node_t1(in, in, in, in, in, in, in, out, out) is det.

fix_3node_t1(K0, V0, K1, V1, four(K00, V00, K01, V01, K02, V02, T00, T01, T02, T03), T1, T2, Tout, RH) ?-
	% steal T0's rightmost subtree and combine it with T1
	Tout = three(K02, V02, K1, V1, three(K00, V00, K01, V01, T00, T01, T02), two(K0, V0, T03, T1), T2),
	RH = no.
fix_3node_t1(K0, V0, K1, V1, three(K00, V00, K01, V01, T00, T01, T02), T1, T2, Tout, RH) ?-
	% steal T0's rightmost subtree and combine it with T1
	Tout = three(K01, V01, K1, V1, two(K00, V00, T00, T01), two(K0, V0, T02, T1), T2),
	RH = no.
fix_3node_t1(K0, V0, K1, V1, two(K00, V00, T00, T01), T1, T2, Tout, RH) ?-
	% move T1 one level down to become the rightmost subtree of T0
	Tout = two(K1, V1, three(K00, V00, K0, V0, T00, T01, T1), T2),
	RH = no.
fix_3node_t1(_K0, _V0, _K1, _V1, empty, _T1, _T2, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = three(K0, V0, K1, V1, T0, T1, T2),
	% The heights of T0 and T2 are unchanged
	% RH = no

% :- pred fix_3node_t2(K, V, K, V, tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_3node_t2(di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_3node_t2(in, in, in, in, in, in, in, out, out) is det.

fix_3node_t2(K0, V0, K1, V1, T0, four(K10, V10, K11, V11, K12, V12, T10, T11, T12, T13), T2, Tout, RH) ?-
	% steal T1's rightmost subtree and combine it with T2
	Tout = three(K0, V0, K12, V12, T0, three(K10, V10, K11, V11, T10, T11, T12), two(K1, V1, T13, T2)),
	RH = no.
fix_3node_t2(K0, V0, K1, V1, T0, three(K10, V10, K11, V11, T10, T11, T12), T2, Tout, RH) ?-
	% steal T1's rightmost subtree and combine it with T2
	Tout = three(K0, V0, K11, V11, T0, two(K10, V10, T10, T11), two(K1, V1, T12, T2)),
	RH = no.
fix_3node_t2(K0, V0, K1, V1, T0, two(K10, V10, T10, T11), T2, Tout, RH) ?-
	% move T2 one level down to become the rightmost subtree of T1
	Tout = two(K0, V0, T0, three(K10, V10, K1, V1, T10, T11, T2)),
	RH = no.
fix_3node_t2(_K0, _V0, _K1, _V1, _T0, empty, _T2, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = three(K0, V0, K1, V1, T0, T1, T2),
	% The heights of T0 and T1 are unchanged
	% RH = no

% :- pred fix_4node_t0(K, V, K, V, K, V,
% 	tree234(K, V), tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_4node_t0(di, di, di, di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_4node_t0(in, in, in, in, in, in, in, in, in, in, out, out) is det.

fix_4node_t0(K0, V0, K1, V1, K2, V2, T0, four(K10, V10, K11, V11, K12, V12, T10, T11, T12, T13), T2, T3, Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = four(K10, V10, K1, V1, K2, V2, two(K0, V0, T0, T10), three(K11, V11, K12, V12, T11, T12, T13), T2, T3),
	RH = no.
fix_4node_t0(K0, V0, K1, V1, K2, V2, T0, three(K10, V10, K11, V11, T10, T11, T12), T2, T3, Tout, RH) ?-
	% steal T1's leftmost subtree and combine it with T0
	Tout = four(K10, V10, K1, V1, K2, V2, two(K0, V0, T0, T10), two(K11, V11, T11, T12), T2, T3),
	RH = no.
fix_4node_t0(K0, V0, K1, V1, K2, V2, T0, two(K10, V10, T10, T11), T2, T3, Tout, RH) ?-
	% move T0 one level down to become the leftmost subtree of T1
	Tout = three(K1, V1, K2, V2, three(K0, V0, K10, V10, T0, T10, T11), T2, T3),
	RH = no.
fix_4node_t0(_K0, _V0, _K1, _V1, _K2, _V2, _T0, empty, _T2, _T3, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
	% The heights of T1, T2 and T3 are unchanged
	% RH = no

% :- pred fix_4node_t1(K, V, K, V, K, V,
% 	tree234(K, V), tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_4node_t1(di, di, di, di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_4node_t1(in, in, in, in, in, in, in, in, in, in, out, out) is det.

fix_4node_t1(K0, V0, K1, V1, K2, V2, T0, T1, four(K20, V20, K21, V21, K22, V22, T20, T21, T22, T23), T3, Tout, RH) ?-
	% steal T2's leftmost subtree and combine it with T1
	Tout = four(K0, V0, K20, V20, K2, V2, T0, two(K1, V1, T1, T20), three(K21, V21, K22, V22, T21, T22, T23), T3),
	RH = no.
fix_4node_t1(K0, V0, K1, V1, K2, V2, T0, T1, three(K20, V20, K21, V21, T20, T21, T22), T3, Tout, RH) ?-
	% steal T2's leftmost subtree and combine it with T1
	Tout = four(K0, V0, K20, V20, K2, V2, T0, two(K1, V1, T1, T20), two(K21, V21, T21, T22), T3),
	RH = no.
fix_4node_t1(K0, V0, K1, V1, K2, V2, T0, T1, two(K20, V20, T20, T21), T3, Tout, RH) ?-
	% move T1 one level down to become the leftmost subtree of T2
	Tout = three(K0, V0, K2, V2, T0, three(K1, V1, K20, V20, T1, T20, T21), T3),
	RH = no.
fix_4node_t1(_K0, _V0, _K1, _V1, _K2, _V2, _T0, _T1, empty, _T3, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
	% The heights of T0, T2 and T3 are unchanged
	% RH = no

% :- pred fix_4node_t2(K, V, K, V, K, V,
% 	tree234(K, V), tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_4node_t2(di, di, di, di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_4node_t2(in, in, in, in, in, in, in, in, in, in, out, out) is det.

fix_4node_t2(K0, V0, K1, V1, K2, V2, T0, T1, T2, four(K30, V30, K31, V31, K32, V32, T30, T31, T32, T33), Tout, RH) ?-
	% steal T3's leftmost subtree and combine it with T2
	Tout = four(K0, V0, K1, V1, K30, V30, T0, T1, two(K2, V2, T2, T30), three(K31, V31, K32, V32, T31, T32, T33)),
	RH = no.
fix_4node_t2(K0, V0, K1, V1, K2, V2, T0, T1, T2, three(K30, V30, K31, V31, T30, T31, T32), Tout, RH) ?-
	% steal T3's leftmost subtree and combine it with T2
	Tout = four(K0, V0, K1, V1, K30, V30, T0, T1, two(K2, V2, T2, T30), two(K31, V31, T31, T32)),
	RH = no.
fix_4node_t2(K0, V0, K1, V1, K2, V2, T0, T1, T2, two(K30, V30, T30, T31), Tout, RH) ?-
	% move T2 one level down to become the leftmost subtree of T3
	Tout = three(K0, V0, K1, V1, T0, T1, three(K2, V2, K30, V30, T2, T30, T31)),
	RH = no.
fix_4node_t2(_K0, _V0, _K1, _V1, _K2, _V2, _T0, _T1, _T2, empty, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
	% The heights of T0, T1 and T3 are unchanged
	% RH = no

% :- pred fix_4node_t3(K, V, K, V, K, V,
% 	tree234(K, V), tree234(K, V), tree234(K, V), tree234(K, V),
% 	tree234(K, V), bool).
% :- mode fix_4node_t3(di, di, di, di, di, di, di, di, di, di, uo, out) is det.
% :- mode fix_4node_t3(in, in, in, in, in, in, in, in, in, in, out, out) is det.

fix_4node_t3(K0, V0, K1, V1, K2, V2, T0, T1, four(K20, V20, K21, V21, K22, V22, T20, T21, T22, T23), T3, Tout, RH) ?-
	% steal T2's rightmost subtree and combine it with T3
	Tout = four(K0, V0, K1, V1, K22, V22, T0, T1, three(K20, V20, K21, V21, T20, T21, T22), two(K2, V2, T23, T3)),
	RH = no.
fix_4node_t3(K0, V0, K1, V1, K2, V2, T0, T1, three(K20, V20, K21, V21, T20, T21, T22), T3, Tout, RH) ?-
	% steal T2's rightmost subtree and combine it with T3
	Tout = four(K0, V0, K1, V1, K21, V21, T0, T1, two(K20, V20, T20, T21), two(K2, V2, T22, T3)),
	RH = no.
fix_4node_t3(K0, V0, K1, V1, K2, V2, T0, T1, two(K20, V20, T20, T21), T3, Tout, RH) ?-
	% move T3 one level down to become the rightmost subtree of T2
	Tout = three(K0, V0, K1, V1, T0, T1, three(K20, V20, K2, V2, T20, T21, T3)),
	RH = no.
fix_4node_t3(_K0, _V0, _K1, _V1, _K2, _V2, _T0, _T1, empty, _T3, _Tout, _RH) ?-
	error("unbalanced 234 tree").
	% Tout = four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
	% The heights of T0, T1 and T2 are unchanged
	% RH = no

%------------------------------------------------------------------------------%

keys(Tree, Keys) :-
	keys_2(Tree, [], Keys).

% :- pred keys_2(tree234(K, V), list(K), list(K)).
% :- mode keys_2(in, in, out) is det.

keys_2(empty, List, List).
keys_2(two(K0, _V0, T0, T1), L0, L) :-
	keys_2(T1, L0, L1),
	keys_2(T0, [K0 | L1], L).
keys_2(three(K0, _V0, K1, _V1, T0, T1, T2), L0, L) :-
	keys_2(T2, L0, L1),
	keys_2(T1, [K1 | L1], L2),
	keys_2(T0, [K0 | L2], L).
keys_2(four(K0, _V0, K1, _V1, K2, _V2, T0, T1, T2, T3), L0, L) :-
	keys_2(T3, L0, L1),
	keys_2(T2, [K2 | L1], L2),
	keys_2(T1, [K1 | L2], L3),
	keys_2(T0, [K0 | L3], L).

%------------------------------------------------------------------------------%

values(Tree, Values) :-
	values_2(Tree, [], Values).

% :- pred values_2(tree234(K, V), list(V), list(V)).
% :- mode values_2(in, in, out) is det.

values_2(empty, List, List).
values_2(two(_K0, V0, T0, T1), L0, L) :-
	values_2(T1, L0, L1),
	values_2(T0, [V0 | L1], L).
values_2(three(_K0, V0, _K1, V1, T0, T1, T2), L0, L) :-
	values_2(T2, L0, L1),
	values_2(T1, [V1 | L1], L2),
	values_2(T0, [V0 | L2], L).
values_2(four(_K0, V0, _K1, V1, _K2, V2, T0, T1, T2, T3), L0, L) :-
	values_2(T3, L0, L1),
	values_2(T2, [V2 | L1], L2),
	values_2(T1, [V1 | L2], L3),
	values_2(T0, [V0 | L3], L).

%------------------------------------------------------------------------------%

:- if(false).

assoc_list_to_tree234(KVs, _) :- var(KVs), !, fail.
assoc_list_to_tree234([], empty).
assoc_list_to_tree234(KVs, Tree) :- KVs = [_|_],
        length(KVs, OriginalN),
        sort(1, <, KVs, SKVs),
        length(SKVs, N),
        ( N < OriginalN -> error("duplicates in assoc_list") ;
            % We will use the minimal tree Depth that can accommodate N
            Depth is integer(floor(ln(N)/ln(4))) + 1,
            make_tree(Depth, N, SKVs, [], Tree)
        ).

    % Make a tree of depth Depth, containing the first N
    % elements of SKVs, returning the tree and the remaining list.
    make_tree(0, N, SKVs, SKVs1, Tree) :- !,
        ( N =\= 0 -> abort
        ; SKVs = SKVs1, Tree = empty
        ).
    make_tree(Depth, N, SKVs, SKVs9, Tree) :-
        % make the widest node such that the subtrees have
        % enough elements for the required depth.
        % It is guaranteed that a tree of Depth can accommodate N elements.
        ( 2^Depth-1 =< N, N =< 4^Depth-1 -> true ; abort ),     % DEBUG
        SubDepth is Depth-1,
        MinSubN is 2^SubDepth-1,
        findall(Width,
            ( N >= 3 + 4*MinSubN, Width=four
            ; N >= 2 + 3*MinSubN, Width=three
            ; N >= 1 + 2*MinSubN, Width=two
            ), Widths),
        writeln(N@Depth:Widths),
        ( N >= 3 + 4*MinSubN ->
            N0 is N-3,          % the total to be put in subtrees
            N4 is N0 div 4,     % actual per subtree (maybe +1)
            Extra is N0 mod 4,  % first Extra need 1 more
            Tree = four(K0,V0,K1,V1,K2,V2,T0,T1,T2,T3),
            N1 is N4 + extra(Extra, 1),
            make_tree(SubDepth, N1, SKVs, SKVs1, T0),
            SKVs1 = [K0-V0|SKVs2],
            N2 is N4 + extra(Extra, 2),
            make_tree(SubDepth, N2, SKVs2, SKVs3, T1),
            SKVs3 = [K1-V1|SKVs4],
            N3 is N4 + extra(Extra, 3),
            make_tree(SubDepth, N3, SKVs4, SKVs5, T2),
            SKVs5 = [K2-V2|SKVs6],
            make_tree(SubDepth, N4, SKVs6, SKVs9, T3)

        ; N >= 2 + 3*MinSubN ->
            N0 is N-2,
            N3 is N0 div 3,
            Extra is N0 mod 3,
            Tree = three(K0,V0,K1,V1,T0,T1,T2),
            N1 is N3 + extra(Extra, 1),
            make_tree(SubDepth, N1, SKVs, SKVs1, T0),
            SKVs1 = [K0-V0|SKVs2],
            N2 is N3 + extra(Extra, 2),
            make_tree(SubDepth, N2, SKVs2, SKVs3, T1),
            SKVs3 = [K1-V1|SKVs4],
            make_tree(SubDepth, N3, SKVs4, SKVs9, T2)

        ; N >= 1 + 2*MinSubN ->
            N0 is N-1,
            N2 is N0 div 2,
            Extra is N0 mod 2,
            Tree = two(K0,V0,T0,T1),
            N1 is N2 + extra(Extra, 1),
            make_tree(SubDepth, N1, SKVs, SKVs1, T0),
            SKVs1 = [K0-V0|SKVs2],
            make_tree(SubDepth, N2, SKVs2, SKVs9, T1)
        ).

    extra(Extra, I, Res) :- Extra >= I -> Res=1 ; Res=0.

:- else.
assoc_list_to_tree234(AssocList, Tree) :-
	assoc_list_to_tree234_2(AssocList, empty, Tree).

% :- pred assoc_list_to_tree234_2(assoc_list(K, V), tree234(K, V),
% 					tree234(K, V)).
% :- mode assoc_list_to_tree234_2(in, in, out) is det.

assoc_list_to_tree234_2([], Tree, Tree).
assoc_list_to_tree234_2([K - V | Rest], Tree0, Tree) :-
	set(Tree0, K, V, Tree1),
	assoc_list_to_tree234_2(Rest, Tree1, Tree).
:- endif.

%------------------------------------------------------------------------------%

tree234_to_assoc_list(Tree, AssocList) :-
	tree234_to_assoc_list_2(Tree, [], AssocList).

% :- pred tree234_to_assoc_list_2(tree234(K, V), assoc_list(K, V),
% 						assoc_list(K, V)).
% :- mode tree234_to_assoc_list_2(in, in, out) is det.

tree234_to_assoc_list_2(empty, List, List).
tree234_to_assoc_list_2(two(K0, V0, T0, T1), L0, L) :-
	tree234_to_assoc_list_2(T1, L0, L1),
	tree234_to_assoc_list_2(T0, [K0 - V0 | L1], L).
tree234_to_assoc_list_2(three(K0, V0, K1, V1, T0, T1, T2), L0, L) :-
	tree234_to_assoc_list_2(T2, L0, L1),
	tree234_to_assoc_list_2(T1, [K1 - V1 | L1], L2),
	tree234_to_assoc_list_2(T0, [K0 - V0 | L2], L).
tree234_to_assoc_list_2(four(K0, V0, K1, V1, K2, V2, T0, T1, T2, T3),
					L0, L) :-
	tree234_to_assoc_list_2(T3, L0, L1),
	tree234_to_assoc_list_2(T2, [K2 - V2 | L1], L2),
	tree234_to_assoc_list_2(T1, [K1 - V1 | L2], L3),
	tree234_to_assoc_list_2(T0, [K0 - V0 | L3], L).

%------------------------------------------------------------------------------%

	% count the number of elements in a tree
count(Tree, N) :-
        count(Tree, 0, N).

count(empty, N, N).
count(two(_, _, T0, T1), N0, N) :-
        N1 is N0+1,
	count(T0, N1, N2),
	count(T1, N2, N).
count(three(_, _, _, _, T0, T1, T2), N0, N) :-
        N1 is N0+2,
	count(T0, N1, N2),
	count(T1, N2, N3),
	count(T2, N3, N).
count(four(_, _, _, _, _, _, T0, T1, T2, T3), N0, N) :-
	N1 is N0+3,
	count(T0, N1, N2),
	count(T1, N2, N3),
	count(T2, N3, N4),
	count(T3, N4, N).


:- comment(well_formed/1, [
	amode:		well_formed(?),
	args:		["Tree":"A 2-3-4 tree"],
	summary:	"Checks whether a tree is well-formed",
	see_also:	[is_tree234/1],
	desc:           html("\
	<P>
        Checks whether the argument is a well-formed 2-3-4 tree.
	</P>
	")
]).

:- export well_formed/1.
well_formed(empty) ?- !.
well_formed(Tree) :-
        well_formed(Tree, 0, _Depth).

well_formed(two(_, _, empty, empty), D0, D) ?- !, D0=D.
well_formed(three(_, _, _, _, empty, empty, empty), D0, D) ?- !, D0=D.
well_formed(four(_, _, _, _, _, _, empty, empty, empty, empty), D0, D) ?- !, D0=D.
well_formed(two(_, _, T0, T1), D0, D) ?-
        D1 is D0+1,
	well_formed(T0, D1, D),
	well_formed(T1, D1, D).
well_formed(three(_, _, _, _, T0, T1, T2), D0, D) :-
        D1 is D0+1,
	well_formed(T0, D1, D),
	well_formed(T1, D1, D),
	well_formed(T2, D1, D).
well_formed(four(_, _, _, _, _, _, T0, T1, T2, T3), D0, D) :-
        D1 is D0+1,
	well_formed(T0, D1, D),
	well_formed(T1, D1, D),
	well_formed(T2, D1, D),
	well_formed(T3, D1, D).


%------------------------------------------------------------------------------%

:- comment(iter_init/2, [
	amode:		iter_init(+,-),
	args:		["Tree":"A 2-3-4 tree",
	                "Iter":"A tree iterator (output)"],
	summary:	"Initialises an iterator for the given tree",
	see_also:	[is_tree234/1, iter_next/4],
	desc:           html("\
	<P>
	Initialises an iterator for the given tree.  The iterator can
        then be used with iter_next/4 to incrementally traverse the tree
        in order of ascending keys.
	</P>
	<P>
        The iterator for an empty tree is always the atom [].
	</P>
	")
]).

:- export iter_init/2.
iter_init(empty, Iter) ?- !, Iter = [].
iter_init(T, [0,T]) :- is_tree234(T).


:- comment(iter_next/4, [
	amode:		iter_next(+,-,-,-),
	args:		["Iter1":"A tree iterator",
			"Key":"A retrieved key",
			"Value":"The retrieved value corresponding to Key",
	                "Iter2":"An updated tree iterator (output)"],
	summary:	"Retrieve the next tree entry using an iterator",
	fail_if:	"Iter1 is the empty iterator ([])",
	see_also:	[iter_init/2,member/3],
	desc:           html("\
	<P>
        Retrieve the next Key-Value entry from the tree position indicated
        by the iterator, and return a new iterator for the next entry.
	</P>
	<P>
        If the retrieved entry is the last one, Iter2 is the empty iterator [].
        The end of iteration can be determined either by testing the
        iterator for being [], or by the failure of iter_next when called
        with an empty iterator.
	</P>
	"),
        eg:"
        % Print all tree entries using recursion:
        print_tree(Tree) :-
            iter_init(Tree, It),
            print_it(It).

        print_it(It1) :-
            ( iter_next(It1, K, V, It2) ->
                writeln(K-V),
                print_it(It2)
            ;
                true
            ).

        % The same using a do-loop:
        print_tree(Tree) :-
            iter_init(Tree, It0),
            ( fromto(It0,It1,It2,[]) do
                iter_next(It1, K, V, It2),
                writeln(K-V)
            ).
        "
]).

:- export iter_next/4.
iter_next([State,T|Up], K, V, Cont) :-
        ( T = two(K0,V0,T0,T1) ->
            ( atom(T0),	                                % T0=T1=empty
                K = K0, V = V0, Cont = Up               % return KV0
            ; compound(T0),
                ( State == 0 -> iter_next([0,T0,1,T|Up], K, V, Cont)	% go into T0
                ; State == 1 -> K = K0, V = V0, Cont = [2,T|Up] 	% return KV0, continue later with T1
                ; State == 2 -> iter_next([0,T1|Up], K, V, Cont)	% go into T1
                )
            )

        ; T = three(K0,V0,K1,V1,T0,T1,T2) ->
            ( atom(T0),                                 % T0=T1=T2=empty
                ( State == 0 -> K = K0, V = V0, Cont = [1,T|Up]
                ; State == 1 -> K = K1, V = V1, Cont = Up
                )
            ; compound(T0),
                ( State == 0 -> iter_next([0,T0,1,T|Up], K, V, Cont)
                ; State == 1 -> K = K0, V = V0, Cont = [2,T|Up]
                ; State == 2 -> iter_next([0,T1,3,T|Up], K, V, Cont)
                ; State == 3 -> K = K1, V = V1, Cont = [4,T|Up]
                ; State == 4 -> iter_next([0,T2|Up], K, V, Cont)
                )
            )

        ; T = four(K0,V0,K1,V1,K2,V2,T0,T1,T2,T3) ->
            ( atom(T0),                                 % T0=T1=T2=T3=empty
                ( State == 0 -> K = K0, V = V0, Cont = [1,T|Up]
                ; State == 1 -> K = K1, V = V1, Cont = [2,T|Up]
                ; State == 2 -> K = K2, V = V2, Cont = Up 
                )
            ; compound(T0),
                ( State == 0 -> iter_next([0,T0,1,T|Up], K, V, Cont)
                ; State == 1 -> K = K0, V = V0, Cont = [2,T|Up]
                ; State == 2 -> iter_next([0,T1,3,T|Up], K, V, Cont)
                ; State == 3 -> K = K1, V = V1, Cont = [4,T|Up]
                ; State == 4 -> iter_next([0,T2,5,T|Up], K, V, Cont)
                ; State == 5 -> K = K2, V = V2, Cont = [6,T|Up]
                ; State == 6 -> iter_next([0,T3|Up], K, V, Cont)
                )
            )
        ).


%------------------------------------------------------------------------------%
% Testing code
%------------------------------------------------------------------------------%

end_of_file.

:- ensure_loaded(library(lists)).
:- import shuffle/2 from lists.

:- export randoms/2.
randoms(N, KVs) :-
        ( for(K,1,N), foreach(K-val,KVSs) do true ),
        shuffle(KVSs, KVs).

:- pragma(debug).
:- export stats/1.
stats(Tree) :-
        sepia_kernel:term_size(Tree, Bytes),
        stats(Tree, 0, 999999, MinD, 0, MaxD, 0, Twos, 0, Threes, 0, Fours),
        count(Tree, N),
        writeln(stats(n=N, min=MinD..MaxD, twos=Twos, threes=Threes, fours=Fours, bytes=Bytes)).

% stats(Tree, Depth, MinD, MaxD, Twos, Threes, Fours)
stats(empty, D, MinD0, MinD, MaxD0, MaxD, Twos, Twos, Threes, Threes, Fours, Fours) :-
        MinD is min(MinD0,D),
        MaxD is max(MaxD0,D).
stats(two(_, _, T0, T1), D0, MinD0, MinD, MaxD0, MaxD, Twos0, Twos, Threes0, Threes, Fours0, Fours) :-
        D1 is D0+1,
        Twos9 is Twos0+1,
	stats(T0, D1, MinD0, MinD1, MaxD0, MaxD1, Twos9, Twos1, Threes0, Threes1, Fours0, Fours1),
	stats(T1, D1, MinD1, MinD,  MaxD1, MaxD,  Twos1, Twos,  Threes1, Threes,  Fours1, Fours).
stats(three(_, _, _, _, T0, T1, T2), D0, MinD0, MinD, MaxD0, MaxD, Twos0, Twos, Threes0, Threes, Fours0, Fours) :-
        D1 is D0+1,
        Threes9 is Threes0+1,
	stats(T0, D1, MinD0, MinD1, MaxD0, MaxD1, Twos0, Twos1, Threes9, Threes1, Fours0, Fours1),
	stats(T1, D1, MinD1, MinD2, MaxD1, MaxD2, Twos1, Twos2, Threes1, Threes2, Fours1, Fours2),
	stats(T2, D1, MinD2, MinD,  MaxD2, MaxD,  Twos2, Twos,  Threes2, Threes,  Fours2, Fours).
stats(four(_, _, _, _, _, _, T0, T1, T2, T3), D0, MinD0, MinD, MaxD0, MaxD, Twos0, Twos, Threes0, Threes, Fours0, Fours) :-
        D1 is D0+1,
        Fours9 is Fours0+1,
	stats(T0, D1, MinD0, MinD1, MaxD0, MaxD1, Twos0, Twos1, Threes0, Threes1, Fours9, Fours1),
	stats(T1, D1, MinD1, MinD2, MaxD1, MaxD2, Twos1, Twos2, Threes1, Threes2, Fours1, Fours2),
	stats(T2, D1, MinD2, MinD3, MaxD2, MaxD3, Twos2, Twos3, Threes2, Threes3, Fours2, Fours3),
	stats(T3, D1, MinD3, MinD,  MaxD3, MaxD,  Twos3, Twos,  Threes3, Threes,  Fours3, Fours).


:- export test/1.
test(N) :-
        randoms(N, KVs),

        % construct
        assoc_list_to_tree234(KVs,T0),
        well_formed(T0),

        % construct by insertion
        init(T00),
        ( foreach(K-V,KVs), fromto(T00,T1,T2,T01) do
            insert(T1, K, V, T2)
        ),
        well_formed(T01),
        count(T01, N),

        % delete all
        shuffle(KVs, KVs1),
        ( foreach(K-_V,KVs1), fromto(T0,T1,T2,T3) do
            delete(T1, K, T2),
            well_formed(T2)
        ),
        is_empty(T3),

        % remove all
        ( foreach(K-V0,KVs1), fromto(T0,T1,T2,T4) do
            remove(T1, K, V, T2),
            V==V0,
            well_formed(T2)
        ),
        is_empty(T4),
        
        % delete half
        N2 is N//2, length(SomeKVs, N2),
        append(SomeKVs, _, KVs),
        shuffle(SomeKVs, SomeKVs1),
        ( foreach(K-_V,SomeKVs1), fromto(T0,T1,T2,T5) do
            delete(T1, K, T2)
        ),
        well_formed(T5),
        count(T5) =:= N-N2,

        % check absence
        ( foreach(K-_V,SomeKVs), param(T5) do
            \+search(T5, K, _)
        ),

        % insert deleted ones back
        shuffle(SomeKVs, SomeKVs2),
        ( foreach(K-V,SomeKVs2), fromto(T5,T1,T2,T6) do
            insert(T1, K, V, T2)
        ),
        well_formed(T6),
        count(T6, N),

        % put all back (half will already be there)
        shuffle(SomeKVs, SomeKVs3),
        ( foreach(K-V,SomeKVs3), fromto(T5,T1,T2,T7) do
            set(T1, K, V, T2)
        ),
        well_formed(T7),
        count(T7, N),

        stats(T0),
        stats(T6),
        stats(T7).



:- export it_test/1.
it_test(N) :-
        randoms(N, KVs),

        % construct
        assoc_list_to_tree234(KVs,T),
        stats(T),

        iter_init(T, It0),
        ( fromto(It0,It1,It2,[]), foreach(K-V,KVs1) do
            iter_next(It1, K, V, It2),
            writeln(K-V)
        ),
        keysort(KVs, KVs1).

:- pragma(nodebug).
:- export it_bench/1.
it_bench(N) :-
        seed(123),
        randoms(N, KVs),

        % construct
        assoc_list_to_tree234(KVs,T),
        stats(T),

        iter_init(T, It0),
        cputime(T0),
        ( fromto(It0,It1,It2,[]) do
            iter_next(It1, _, _, It2)
        ),
        Time is cputime-T0,
        writeln(cputime=Time).


