Prolog: First duplicate value

前端 未结 4 1783
挽巷
挽巷 2020-11-30 15:30

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.

相关标签:
4条回答
  • 2020-11-30 15:34

    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.
    
    0 讨论(0)
  • 2020-11-30 15:37

    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.
    
    0 讨论(0)
  • 2020-11-30 15:48

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

    0 讨论(0)
  • 2020-11-30 16:01

    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).
    
    0 讨论(0)
提交回复
热议问题