Prolog: First duplicate value

霸气de小男生 提交于 2019-11-27 05:36:41

Here is a pure version using dif/2 which implements sound inequality. dif/2 is offered by B-Prolog, YAP-Prolog, SICStus-Prolog and SWI-Prolog.

firstdup(E, [E|L]) :-
    member(E, L).
firstdup(E, [N|L]) :-
    non_member(N, L),
    firstdup(E, L).

member(E, [E|_L]).
member(E, [_X|L]) :-
    member(E, L).

non_member(_E, []).
non_member(E, [F|Fs]) :-
    dif(E, F),
    non_member(E, Fs).

The advantages are that it can also be used with more general queries:

?- firstdup(E, [A,B,C]).
E = A, A = B ;
E = A, A = C ;
E = C,
B = C,
dif(A, C) ;
false.

Here, we get three answers: A is the duplicate in the first two answers, but on two different grounds: A might be equal to B or C. In the third answer, B is the duplicate, but it will only be a duplicate if C is different to A.

To understand the definition, read :- as an arrow ← So what is on the right-hand side is what you know and on the left is what you conclude. It is often in the beginning a bit irritating to read predicates in that direction, after all you might be tempted to follow "the thread of execution". But often this thread leads to nowhere – it gets too complex to understand.

The first rule reads:

Provided E is an element of list L we conclude that [E|L] has E as first duplicate.

The second rule reads:

Provided E is the first duplicate of L (don't panic here and say we don't know that ...) and provided that N is not an element of L we conclude that [N|L] has E as first duplicate.

The member/2 predicate is provided in many Prolog systems and non_member(X,L) can be defined as maplist(dif(X),L). Thus one would define firstdup/2 rather as:

firstdup(E, [E|L]) :-
    member(E, L).
firstdup(E, [N|L]) :-
    maplist(dif(N), L),
    firstdup(E, L).
repeat

In this answer we improve the logically pure code presented in this earlier answer. Step-by-step:

  1. 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).
    
  2. 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)
       ).
    
  3. 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)
       ).
    
  4. Let's hoist memberd_t/3!

    firstdup(E,[X|Xs]) :-
       memberd_t(X,Xs,T),
       (  T=true
       -> E=X      
       ;  T=false,
          firstdup(E,Xs)
       ).
    
  5. 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.

rep(N,List) :- append(L1,[N|_],List),append(_,[N|_],L1),\+(rep(_,L1)).

Not sure if this is homework/ there are restrictions on which predicates you are allowed to use, but this gets prolog to do the recursion for you.

It says.. find all duplicates ie. where there is an item with list index I1, and another one with I2, such that they both have the same value N, and the indices are not the same ie.don't consider the same list item as a duplicate.

These duplicates are put (in the order they are found, from the beginning crucially) in list AllDups, and the predicate is true if the first duplicate found unifies with M, the value you are checking.

First Attempt: This works but is very inefficient, building a list of ALL duplicates

prep(M, List) :-
    findall(N,
        (nth0(I1, List, N),
        nth0(I2, List, N),
        not(I1 =:= I2)),
        AllDups),
    nth1(1, AllDups, M).


?- prep(3,[1,3,5,3,5]).
true.

?- prep(5,[1,3,5,3,5]).
false.

Even if you are not allowed to use findall, it might help you work out how to do it 'manually'.

Second Attempt: This doesn't work / backtracks too far yielding a false positive

You can even do it without the findall - use nth0 to find the first duplicate item, then if that unifies with the value you are checking, it returns true, otherwise false.

prep(N, List) :-
        nth0(I1, List, N),
        nth0(I2, List, N),
        not(I1 =:= I2).

Third Attempt : This works, and returns as soon as the first duplicate has been found

prep(M, List) :-
        nth0(I1, List, N),
        nth0(I2, List, N),
        not(I1 =:= I2), !,
        M == N.
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!