2

The aim is to implement the predicate noDupl/2.

The first argument of this predicate is the list to analyze and second argument is the list of numbers which are no duplicate.

I could not understand code below and when I compiled it, it gave an error message that contained is undefined procedure, however as a hint it is written that we can use as predefined predicate contained and notContained. I think I need to define contained and notContained.

noDupl(XS, Res):-
   help( XS, [],Res).

help([],_,[]).
help([X|XS],Seen,[X|Res]):-
   notContained(X,XS),
   notContained(X,Seen), 
   help(XS, [X|Seen], Res).
help([X|XS],Seen,Res):-
   contained(X,Seen),
   help(XS, Seen, Res).
help([X|XS],Seen,Res):-
   contained(X,XS),
   help(XS, [X|Seen], Res).

Could someone please explain me the problem.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
limonik
  • 499
  • 1
  • 6
  • 28

3 Answers3

3

The missing definitions might be:

contained(X,[X|_]).
contained(X,[E|Es]) :-
   dif(X, E),
   contained(X, Es).

notContained(_X, []).
notContained(X, [E|Es]) :-
   dif(X, E),
   notContained(X, Es).

(I like to call these relations rather memberd/2 and non_member/2.)

The definition you gave extends the relation with an extra argument for the elements considered so far.

To understand the meaning of each clause, read each right-to-left in the direction of the arrow (the :- is a 1970's ASCII-fication of ). Let's take the first rule:

Provided, that X is not an element of XS, and
provided, that X is not an element of Seen, and
provided, that help(X, [X|Seen], Res) is true,


then also help([X|XS],Seen,[X|Res]) is true.

In other words, if X is neither in the list of visited elements Seen nor in the elements yet to be visited XS, then it does not possess a duplicate.

What is a bit difficult to understand is whether or not the clauses you gave are mutually exclusive - this is, strictly speaking, not your concern, as long as you are only interested in declarative properties, but it is a good idea to avoid such redundancies.

Here is a case, where such redundancy shows:

?- noDupl([a,a,a],U).
   U = []
;  U = []
;  false.

Ideally, the system would give one determinate answer:

?- noDupl([a,a,a], U).
   U = [].

Personally, I do not like a lot to split things into too many cases. Essentially, we could have two: it is a duplicate, and it is none.

It is possible to provide a definition that is correct and still fully determinate for the cases where determinism is possible - such as when the first argument is "sufficiently instantiated" (which includes a ground list). Let's see if there are some answers into that direction.

false
  • 10,264
  • 13
  • 101
  • 209
2

I've annotated your code for you:

noDupl( XS , Res ) :- % Res is the [unique] set of element from the bag XS
  help( XS, [],Res)    % if invoking the helper succeeds.
  .                    %

help( []     , _    , []      ) .  % the empty list is unique.
help( [X|XS] , Seen , [X|Res] ) :- % A non-empty list is unique, if...
   notContained(X,XS),             % - its head (X) is not contained in its tail (XS), and
   notContained(X,Seen),           % - X has not already been seen, and
   help(XS, [X|Seen], Res).        % - the remainder of the list is unique.
help( [X|XS] , Seen , Res ) :-     % otherwise...
   contained(X,Seen) ,             % - if X has been seen,
   help(XS, Seen, Res).            % - we discard it and recurse down on the tail.
help([X|XS],Seen,Res):-            % otherwise...
   contained(X,XS),                % - if X is in the tail of the source list, 
   help(XS, [X|Seen], Res).        % - we discard it (but add it to 'seen').

Your contained/2 and notContained/2` predicates might be defined as this:

contained( X , [X|_]  ) :- ! .
contained( X , [Y|Ys] ) :- X \= Y , contained( X , Ys ) .

not_contained( _ , [] ) .
not_contained( X , [Y|Ys] ) :- X \= Y , not_contained(X,Ys) .

Now, I may be missing something in your code, but there's an awful lot of redundancy in it. You could simply write something like this (using the built-ins member/2 and reverse/2):

no_dupes( List , Unique ) :- no_dupes( Bag , [] , Set ) .

no_dupes( [] , V , S ) .      % if we've exhausted the bag, the list of visited items is our set (in reverse order of the source)
  reverse(V,S)                % - reverset it 
  .                           % - to put our set in source order
no_dupes( [X|Xs] , V , S ) :- % otherwise ...
  ( member(X,V) ->            % - if X is already in the set, 
    V1 = V                    % -   then we discard X
  ; V1 = [X|V]                % -   else we add X to the set 
  ) ,                         % And...
  no_dupes( Xs , V1 , S )     % we recurse down on the remainder
  .                           % Easy!
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
2

Can this be done in a pure and efficient way? Yes, by using tpartition/4 and (=)/3 like so:

dups_gone([]    ,[]).
dups_gone([X|Xs],Zs0) :-
   tpartition(=(X),Xs,Ts,Fs),
   if_(Ts=[], Zs0=[X|Zs], Zs0=Zs),
   dups_gone(Fs,Zs).

Some sample ground queries (all of which succeed deterministically):

?- dups_gone([a,a,a],Xs).
Xs = [].

?- dups_gone([a,b,c],Xs).
Xs = [a, b, c].

?- dups_gone([a,b,c,b],Xs).
Xs = [a, c].

?- dups_gone([a,b,c,b,a],Xs).
Xs = [c].

?- dups_gone([a,b,c,b,a,a,a],Xs).
Xs = [c].

?- dups_gone([a,b,c,b,a,a,a,c],Xs).
Xs = [].

This also works with more general queries. Consider:

?- length(Xs,N), dups_gone(Xs,Zs).
  N = 0, Xs = [],            Zs = []
; N = 1, Xs = [_A],          Zs = [_A]
; N = 2, Xs = [_A,_A],       Zs = []
; N = 2, Xs = [_A,_B],       Zs = [_A,_B],    dif(_A,_B)
; N = 3, Xs = [_A,_A,_A],    Zs = []
; N = 3, Xs = [_A,_A,_B],    Zs = [_B],       dif(_A,_B)
; N = 3, Xs = [_A,_B,_A],    Zs = [_B],       dif(_A,_B)
; N = 3, Xs = [_B,_A,_A],    Zs = [_B],       dif(_A,_B), dif(_A,_B)
; N = 3, Xs = [_A,_B,_C],    Zs = [_A,_B,_C], dif(_A,_B), dif(_A,_C), dif(_B,_C)
; N = 4, Xs = [_A,_A,_A,_A], Zs = [] 
...
Community
  • 1
  • 1
repeat
  • 18,496
  • 4
  • 54
  • 166
  • @false. Thx! Why not use [tag:dcg] and [`if_//3`](http://stackoverflow.com/a/29366458/4609915)? It *would* be more concise---but what's a proper name? – repeat Jul 14 '15 at 12:13