Search code examples
listprologduplicates

Remove duplicate from a list but not returning two same results in SWI-Prolog?


duplicate([],[]).
duplicate([A|B],[A|B1]) :- not(member(A,B)), duplicate(B,B1).
duplicate([A|B],List) :- member(A,B), duplicate(B,List).

I wrote this predicate to remove duplicate from the list, but when I test it,

?- duplicate([a,b,c,a,d,c,b,a,e,f],N).
N = [d, c, b, a, e, f] ;
N = [d, c, b, a, e, f] ;
false.

Is there a way to just keep one result only, not two same results? (so it will only return one list).

Also, I am not allowed to use operators that modify the backtracking search, such as the cut operator !, the negation operators not, +, or the if-then-else operator with -> and ;

It would be grateful if someone could help me . :D


Solution

  • The actual reason for receiving more than one answer is the goal member(A,As). It produces multiple answers for duplicates in As.

    ?- member(a, [a,a]).
       true
    ;  true.
    

    There are several ways out.

    memberchk/2 or once/1

    memberchk/2 is defined as

    memberchk(X, Xs) :-
       once(member(X, Xs)).
    

    This removes alternate answers. But then, it may remove otherwise valid solutions too. Consider:

    ?-        memberchk(X, [a,b]), b = X.
       false.
    ?- b = X, memberchk(X, [a,b]), b = X.
       b = X.
    

    So memberchk/2 is sensitive to the precise instantiation, which makes it a very brittle, impure predicate.

    But it has one good point: It sticks to just one answer for

    ?- memberchk(a, [a,a]).
       true.
    

    So what would be ideal is a definition that is both pure and sticking to the first element. Enter

    memberd/2

    memberd(X, [X|_Ys]).
    memberd(X, [Y|Ys]) :-
       dif(X, Y),
       memberd(X, Ys).
    

    In this definition, the recursive rule is only of relevance if the list element is different. Thus this rule will never apply to memberd(a, [a,a,a]).

    Another problem in your definition is not(member(A,B)) which only behaves as intended, if A and B are sufficiently instantiated. Your definition fails for: duplicate([a,X],[a,b]). although there is a solution: X = b.

    Rather replace it by non_member/2.

    Alternatively, in case you are interested in the most efficient solution, consider library(reif) available for SICStus and SWI which leads to:

    list_nub([], []).
    list_nub([X|Xs], Ys0) :-
       if_(memberd_t(X, Xs),  Ys0 = Ys1, Ys0 = [X|Ys1]),
       list_nub(Xs, Ys1).