I need to find the first duplicate value in a list.
prep(3,[1,3,5,3,5]). Should be true.
prep(5,[1,3,5,3,5]). Should be false.
In this answer we improve the logically pure code presented in this earlier answer. Step-by-step:
We combine two predicates memberd/2 and non_member/2 to one, memberd_t/3, which uses an additional argument for reifying list membership into a truth-value (true or false).
memberd_t/3 is equivalent to memberd/2 + non_member/2, so we could define it like this:
memberd_t(X,Xs,true) :- memberd(X,Xs). memberd_t(X,Xs,false) :- non_member(X,Xs).
Or, vice versa, we could define memberd/2 and non_member/2 like so:
memberd(X,Xs) :- memberd_t(X,Xs,true). non_member(X,Xs) :- memberd_t(X,Xs,false).
In practise, we use a tuned implementation of memberd_t/3—one with better determinism.
Let's see that memberd_t/3 in fact covers both memberd/2 and non_member/2!
?- memberd_t(X,[1,2,3],T). T = true , X=1 ; T = true , X=2 ; T = true , X=3 ; T = false, dif(X,1), dif(X,2), dif(X,3).
Take the following variant of predicate firstdup/2 (defined earlier) as our starting point:
firstdup(E,[X|Xs]) :-
( memberd(X,Xs),
E=X
; non_member(X,Xs),
firstdup(E,Xs)
).
Let's replace the use of memberd/2 and non_member/2 with memberd_t/3!
firstdup(E,[X|Xs]) :-
( memberd_t(X,Xs,true),
E=X
; memberd_t(X,Xs,false),
firstdup(E,Xs)
).
Let's hoist memberd_t/3!
firstdup(E,[X|Xs]) :-
memberd_t(X,Xs,T),
( T=true
-> E=X
; T=false,
firstdup(E,Xs)
).
Above pattern pred_t(OtherArgs,T), (T = true -> Then ; T = false, Else) can be expressed more concisely using if_/3, writing if_(pred_t(OtherArgs),Then,Else).
firstdup(E,[X|Xs]) :-
if_(memberd_t(X,Xs),
E=X,
firstdup(E,Xs)).
Let's run some queries!
?- firstdup(1,[1,2,3,1]).
true. % succeeds deterministically
?- firstdup(X,[1,2,3,1]).
X = 1. % succeeds deterministically
?- firstdup(X,[A,B,C]). % succeeds, leaving behind a choicepoint
A=X , B=X % ... to preserve the full solution set.
; A=X , dif(B,X), C=X
; dif(A,X), B=X , C=X
; false.