%   File   : BAGUTL.PL
%   Author : R.A.O'Keefe
%   Updated: 18 February 1984
%   Purpose: Bag Utilities
/*
    A bag B is a function from a set dom(B) to the non-negative integers.
For the purposes of this module, a bag is constructed from two functions:
	
	bag		- creates an empty bag
	bag(E, M, B)	- extends the bag B with a new (NB!) element E
			  which occurs with multiplicity M, and which
			  precedes all elements of B in Prolog's order.

For instance the bag with an a and two bs in it is represented by the
term
	bag(a,1,bag(b,2,bag)).

A bag is represented by a Prolog term mirroring its construction.  There
is one snag with this: what are we to make of
	bag(f(a,Y), 1, bag(f(X,b), 1, bag))	?
As a term it has two distinct elements, but f(a,b) will be reported as
occurring in it twice.  But according to the definition above,
	bag(f(a,b), 1, bag(f(a,b), 1, bag))
is not the representation of any bag, that bag is represented by
	bag(f(a,b), 2, bag)
alone.  We are apparently stuck with a scheme which is only guaranteed
to work for "sufficiently instantiated" terms, but then, that's true of 
a lot of Prolog code.

    The reason for insisting on the order is to make union and 
intersection linear in the sizes of their arguments.

*/

% Defined in this file
%	bag_inter/3,
%	bag_to_list/2,
%	bag_to_set/2,
%	bag_union/3,
%	bagmax/2,
%	bagmin/2,
%	checkbag/2,
%	is_bag/1,
%	length/3,
%	list_to_bag/2,
%	make_sub_bag/2,
%	mapbag/3,
%	member/3,
%	member/3,
%	portray_bag/1,
%	test_sub_bag/2.
%

is_bag(bag).
is_bag(bag(E,M,B)) :-
	integer(M), M > 0,
	is_bag(B, E).

	is_bag(bag, _).
	is_bag(bag(E,M,B), P) :-
		E @> P,
		integer(M), M > 0,
		is_bag(B, E).



portray_bag(bag(E,M,B)) :-
	write('[% '), portray_bag(E, M, B), write(' %]').
portray_bag(bag) :-
	write('[% '), write(' %]').

	portray_bag(E, M, B) :-
		var(B), !,
		portray_bag(E, M), write(' | '), write(B).
	portray_bag(E, M, bag(F,N,B)) :- !,
		portray_bag(E, M), write(', '),
		portray_bag(F, N, B).
	portray_bag(E, M, bag) :- !,
		portray_bag(E, M).
	portray_bag(E, M, B) :-
		portray_bag(E, M), write(' | '), write(B).

		portray_bag(E, M) :-
			print(E), write(':'), write(M).


%   If bags are to be as useful as lists, we should provide mapping
%   predicates similar to those for lists.  Hence
%	checkbag(Pred, Bag)		- applies Pred(Element, Count)
%	mapbag(Pred, BagIn, BagOut)	- applies Pred(Element, Answer)
%   Note that mapbag does NOT give the Count to Pred, but preserves it.
%   It wouldn't be hard to apply Pred to four arguments if it wants them.



checkbag(_, bag).
checkbag(Pred, bag(E,M,B)) :-
	apply(Pred, [E, M]),
	checkbag(Pred, B).


mapbag(Pred, BagIn, BagOut) :-
	mapbaglist(Pred, BagIn, Listed),
	keysort(Listed, Sorted),
	bagform(Sorted, BagOut).

	mapbaglist(_, bag, []).
	mapbaglist(Pred, bag(E,M,B), [R-M|L]) :-
		apply(Pred, [E, R]),
		mapbaglist(Pred, B, L).



bag_to_list(bag, []).
bag_to_list(bag(E,M,B), R) :-
	bag_to_list(M, E, L, R),
	bag_to_list(B, L).

	bag_to_list(0, _, L, L) :- !.
	bag_to_list(M, E, L, [E|R]) :-
		N is M-1,
		bag_to_list(N, E, L, R).



list_to_bag(L, B) :-
	addkeys(L, K),
	keysort(K, S),
	bagform(S, B).

	addkeys([], []).
	addkeys([Head|Tail], [Head-1|Rest]) :-
		addkeys(Tail, Rest).

	bagform([], bag) :- !.
	bagform(List, bag(E,M,B)) :-
		bagform(E, List, Rest, 0, M), !,
		bagform(Rest, B).

		bagform(Head, [Head-N|Tail], Rest, K, M) :-!,
			L is K+N,
			bagform(Head, Tail, Rest, L, M).
		bagform(_, Rest, Rest, M, M).



bag_to_set(bag, []).
bag_to_set(bag(E,_,B), [E|S]) :-
	bag_to_set(B, S).


/*  There are two versions of the routines member, bagmax, and bagmin.
    The slow versions, which are commented out, try to allow for the
    possibility that distinct elements in the bag might unify, while
    the faster routines assume that all elements are ground terms.


member(E, M, bag(E,K,B)) :-
	member(B, E, K, M).
member(E, M, bag(_,_,B)) :-
	member(E, M, B).

	member(bag(E,L,B), E, K, M) :- !,
		N is K+L,
		member(B, E, N, M).
	member(bag(_,_,B), E, K, M) :-
		member(B, E, K, M).
	member(bag,	   E, M, M).

%  These routines are correct, but Oh, so costly!

bagmax(B, E) :-
	member(E, M, B),
	\+ (member(F, N, B), N > M).

bagmin(B, E) :-
	member(E, M, B),
	\+ (member(F, N, B), N < M).

*//*	The faster versions follow    */


member(Element, Multiplicity, bag(Element,Multiplicity,_)).
member(Element, Multiplicity, bag(_,_,Bag)) :-
	member(Element, Multiplicity, Bag).


memberchk(Element, Multiplicity, bag(Element,Multiplicity,_)) :- !.
memberchk(Element, Multiplicity, bag(_,_,Bag)) :-
	memberchk(Element, Multiplicity, Bag).



bagmax(bag(E,M,B), Emax) :-
	bag_scan(B, E, M, Emax, >).

bagmin(bag(E,M,B), Emin) :-
	bag_scan(B, E, M, Emin, <).

	bag_scan(bag(Eb,Mb,B), _, Mi, Eo, C) :-
		compare(C, Mb, Mi), !,
		bag_scan(B, Eb, Mb, Eo, C).
	bag_scan(bag(_,_,B), Ei, Mi, Eo, C) :-
		bag_scan(B, Ei, Mi, Eo, C).
/*	bag_scan(bag(Eb,Mb,B), Ei, Mi, Eo, C) :-
		bag_scan(B, Eb, Mb, Eo, C).	%  for all extrema
*/	bag_scan(bag,	       Ei, _, Ei, _).




length(B, BL, SL) :-
	length(B, 0, BL, 0, SL).

	length(bag,	   BL, BL, SL, SL).
	length(bag(_,M,B), BA, BL, SA, SL) :-
		BB is BA+M, SB is SA+1,
		length(B, BB, BL, SB, SL).


%  sub_bag, if it existed, could be used two ways: to test whether one bag
%  is a sub_bag of another, or to generate all the sub_bags.  The two uses
%  need different implementations.


make_sub_bag(bag, bag).
make_sub_bag(bag(E,M,B), bag(E,N,C)) :-
	countdown(M, N),
	make_sub_bag(B, C).
make_sub_bag(bag(_,_,B), C) :-
	make_sub_bag(B, C).

	countdown(M, M).
	countdown(M, N) :-
		M > 1, K is M-1,
		countdown(K, N).



test_sub_bag(bag, _).
test_sub_bag(bag(E1,M1,B1), bag(E2,M2,B2)) :-
	compare(C, E1, E2),
	test_sub_bag(C, E1, M1, B1, E2, M2, B2).

	test_sub_bag(>, E1, M1, B1, _, _, B2) :-
		test_sub_bag(bag(E1, M1, B1), B2).
	test_sub_bag(=, E1, M1, B1, E1, M2, B2) :-
		M1 =< M2,
		test_sub_bag(B1, B2).


bag_union(bag(E1,M1,B1), bag(E2,M2,B2), B3) :-
	compare(C, E1, E2), !,
	bag_union(C, E1, M1, B1, E2, M2, B2, B3).
bag_union(bag, Bag, Bag) :- !.
bag_union(Bag, bag, Bag).

	bag_union(<, E1, M1, B1, E2, M2, B2, bag(E1,M1,B3)) :-
		bag_union(B1, bag(E2, M2, B2), B3).
	bag_union(>, E1, M1, B1, E2, M2, B2, bag(E2,M2,B3)) :-
		bag_union(bag(E1, M1, B1), B2, B3).
	bag_union(=, E1, M1, B1, E1, M2, B2, bag(E1,M3,B3)) :-
		M3 is M1+M2,
		bag_union(B1, B2, B3).



bag_inter(bag(E1,M1,B1), bag(E2,M2,B2), B3) :-
	compare(C, E1, E2), !,
	bag_inter(C, E1, M1, B1, E2, M2, B2, B3).
bag_inter(_, _, bag).

	bag_inter(<, _, _, B1, E2, M2, B2, B3) :-
		bag_inter(B1, bag(E2,M2,B2), B3).
	bag_inter(>, E1, M1, B1, _, _, B2, B3) :-
		bag_inter(bag(E1,M1,B1), B2, B3).
	bag_inter(=, E1, M1, B1, E1, M2, B2, bag(E1, M3, B3)) :-
		(   M1 < M2, M3 = M1  ;  M3 = M2   ), !,
		bag_inter(B1, B2, B3).


