[comp.lang.prolog] uniqueness test

hoppe@ipsi.UUCP (Heinz-Ulrich Hoppe) (03/26/91)

In several of my programs, I use a meta predicate I call  u n i q u e. 
unique(+Expr,+Goal) is similar to findall/3 or setof/3 in that it 
takes a goal as its second argument and an expression, typically 
sharing variables with Goal, as its first argument. unique/2 succeeds 
and instantiates Expr iff there is exactly one instantiation of Expr 
(irrespective of different naming of variables) derivable from any 
possible proof of Goal.

Examples:	?- unique(X,member(X,[1,2,3])).
		no

		?- unique([X,Y],( member(X,[1,2,1]),
			          member(Y,[2,3,2]),
			          X + Y =:= 3       ) ).
		X = 1
		Y = 2

For my applications of unique/2, the following requirements have to 
be met:
- The implementation has to allow for nested use (e.g. a goal inside 
  unique may directly or indirectly involve another call of unique).
- After having found two  d i f f e r e n t  solutions (in terms of 
  Expr), the computation has to terminate (with failure). This is not 
  only for efficiency reasons, but also allows for the processing of 
  goals with a potentially infinite number of (different) solutions.

In the following, I list two different implementations of unique/2, one 
correct and one incorrect. I am interested in any comments on the problem 
in general, but specifically in answers to the following questions:

(1) Do you know of any Prolog implementation where the uniqueness test 
    can be directly performed by a built-in predicate (in accordance with 
    the computational requirements)?

(2) Do you know of different implementations of a uniqueness test in 
    Prolog? (Please give ref. or send code!)

(3) Do you have suggestions how to improve the two versions listed?

Ulrich Hoppe
GMD-IPSI 
Dolivostr. 15
6100 Darmstadt (FRG)
hoppe@ipsi.darmstadt.gmd.dbp.de

________________________________________________________________________

% Version 1:      unique1(+Expr,+Goal) 
% Comments:
%   (1) A copy of the original goal with fresh variables is
%	generated as a reference term for comparison with future 
% 	instantiations which may be pairwise incompatible (in the 
%	instantiation of variables  n o t  shared with	Expr).
%   (2) A marker '$unitest'(Expr,Goal) may be left in the database
%	by a previous successful call of Goal. 
%	db_unify_fact('$unitest'(Expr,CGoal)) is used instead of
%	clause('$unitest'(Expr1,CGoal),true) in order to avoid the 
%	following behavior:
%		?- unique1(X,member(X,[[a,_],[a,1],[a,2]])).
%		X       = [a,_648]
%		?- unique1(X,member(X,[[a,1],[a,_],[a,2]])).
%		no
%	Instead, unique1 should fail (and does) in both cases. 
%	Still, it succeeds on
%		?-  unique1(X,member(X,[[a,_],[a,1]])).
%	giving  X = [a,1] irrespective of the order of elements.  
%	So, unique specialization of a solution is allowed.
%	"Fail" in this case causes a loop over call(Goal).
%   (3) If a previous solution exists and cannot be unified
%	with the current one, the database is cleared and 
%	and test_unique1/3 terminates with failure.
%   (4)	If there is no previous solution, the current solution
%	is asserted and "fail" causes the loop.
% Nested use is possible, because the different DB assertions are 
% distinguished by the goal. Given an original goal whose execution 
% terminates, we can be sure that it cannot appear again as a subgoal 
% of itself in the calling hierarchy (otherwise contradiction to non-
% termination). 
%

unique1(Expr,Goal) :-
	copy(Goal,CGoal), 	% (1)
	!,
	test_unique(Expr,Goal,CGoal).

test_unique1(Expr,Goal,CGoal) :-
	call(Goal), 
	(  db_unify_fact('$unitest'(Expr,CGoal)),	% (2)
	   fail
	 ; clause('$unitest'(Expr1,CGoal),true),	% (3)
	   Expr1 \= Expr,
	   !,
	   retract('$unitest'(Expr1,CGoal)),
	   fail 
	 ; not clause('$unitest'(Expr,CGoal),true),	% (4)
	   asserta('$unitest'(Expr,Goal)),
	   fail  ).
test_unique1(Expr,Goal,CGoal) :-
	retract('$unitest'(Expr,Goal)).

db_unify_fact(Fact) :-
	retract(Fact),
	asserta(Fact).


% Version 2:      unique2(+Expr,+Goal) 
% Comments:
% This version behaves exactly as unique1/2 if in test_unique1/3
% db_unify_fact(<marker>) were replaced by clause(<marker>,true).
% It does not require DB-interactions (except potentially for copy), 
% but is computationally less efficient than version 1 in that the 
% first solution of Goal (if there is one) is always calculated twice.
%   

unique2(Expr,Goal) :-
        copy(p(Expr,Goal),p(CExpr,CGoal)),
        call(Goal),
        !,
        alt_test(Expr,CExpr,CGoal).

test_unique2(Expr,CExpr,CGoal) :-
        call(CGoal), 
        (  Expr \= CExpr, !, fail
         ; fail ).
test_unique2(_,_,_) :- true.

hoppe@ipsi.UUCP (Heinz-Ulrich Hoppe) (03/27/91)

Of course, the two predicates unique1 and unique2 have to be defined as:

> unique1(Expr,Goal) :-
> 	copy(Goal,CGoal),
> 	!,
> 	test_unique1(Expr,Goal,CGoal).
	^^^^^^^^^^^^

> unique2(Expr,Goal) :-
> 	copy(p(Expr,Goal),p(CExpr,CGoal)),
> 	call(Goal),
> 	!,
> 	test_unique2(Expr,CExpr,CGoal).
	^^^^^^^^^^^^

Ulrich Hoppe
GMD-IPSI 
Dolivostr. 15
6100 Darmstadt (FRG)
hoppe@ipsi.darmstadt.gmd.dbp.de

tom@quintus.UUCP (Tom Howland) (03/27/91)

Regarding the question about a "unique" predicate,

wouldn't this do it?

unique(X,G):-findall(X,G,L),sort(L,[X]).

- Tom

jm@sics.se (Johan Montelius) (03/28/91)

>wouldn't this do it?

>unique(X,G):-findall(X,G,L),sort(L,[X]).

Does it work in Quintus? Try findall(X,member(X,[1,2,3|_]),L)
and try to sort L. It does not work in SICStus. It does however
work in AKL.

unique(L,Goal):- bagof(L1,Goal), filter(L1,L).

| ?- unique([A],X\member(X,[1,2,3|_])).

no
| ?- 

	  **** AKL****
    Soon at a theatre near you.

--
Johan Montelius 
SICS, PO Box 1263, S-164 28 Kista, Sweden.	
Phone: +46 8 752 15 68     Telefax: +46 8 751 72 30       Internet: jm@sics.se