2

In Prolog how do you write a procedure that can be used to test whether or not a list represents a set with no duplicates?

General_9
  • 2,249
  • 4
  • 28
  • 46

6 Answers6

4

There are several ways to do this. @thanosQR is right in pointing to SWI-Prolog's is_set/1, but if you want a portable solution, you can define that predicate in terms of setof:

is_set(Lst) :-
    setof(X, member(X, Lst), Set),
    length(Lst, N),
    length(Set, N).

A list contains no duplicates if its number of elements is equal to the number of elements in the setof its elements.

You can also use the (I believe non-standard, but commonly available) sort/2, which eliminates duplicates:

is_set(Lst) :-
    sort(Lst, Set),
    length(Lst, N),
    length(Set, N).

This takes O(n lg n) time to run.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
3

We define distinct_t/2 based on if_/3 and memberd_t/3:

distinct_t([], true).
distinct_t([X|Xs], T) :-
   if_(memberd_t(X,Xs), T=false, distinct_t(Xs,T)).

Let's start with the most general use of distinct_t/2:

?- distinct_t(Xs,T).
   T = true , Xs = []
;  T = true , Xs = [_A]
;  T = false, Xs = [_A,_A|_B]
;  T = true , Xs = [_A,_B],       dif(_B,_A)
;  T = false, Xs = [_A,_B,_A|_C], dif(_B,_A)
;  T = false, Xs = [_A,_B,_B],    dif(_B,_A), dif(_B,_A)
;  T = true , Xs = [_A,_B,_C],    dif(_B,_A), dif(_C,_A), dif(_C,_B)
...

Alright! How about this quite general query?

?- distinct_t([1,2,X,3],T).
   T = false, X = 1
;  T = false, X = 2
;  T = false, X = 3
;  T = true , dif(X,1), dif(X,2), dif(X,3).

If we are only interested in cases with T = true, we use dictinct/1—defined like this:

distinct(Xs) :-
   distinct_t(Xs, true).

Sample queries:

?- distinct([1,2,3,1]).
false.
?- distinct([1,2,3,2]).
false.
?- distinct([1,2,3,3]).
false.
?- distinct([1,2,3,4]).
true.                     % succeeds deterministically
repeat
  • 18,496
  • 4
  • 54
  • 166
2

Using sort/2, ground/1, same_length/2, and iwhen/2:

:- use_module(library(lists)).

is_set(S) :-
   iwhen(ground(S), (same_length(S,T), sort(S,T))).

Sample uses:

?- is_set([4,1,3,2]).        % not in standard order
true.                        % OK: no duplicates

?- is_set([1,2,3,1,2]).
false.

?- is_set([1,2,3,1,X]).
ERROR: Arguments are not sufficiently instantiated
repeat
  • 18,496
  • 4
  • 54
  • 166
2

First, define a predicate to make sure a value isn't in a list:

notin(A,[]).
notin(A,[B|C]) :- A\=B, notin(A,C).

Then, our nodups predicate makes sure each element doesn't appear in the part of the list that comes after it:

nodups([]).
nodups([_]).
nodups([A|B]) :- notin(A,B), nodups(B).
Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • This requires O(n²) time to run. See my answer for an O(n lg n) solution. (Besides, `notin(X,Lst)` can be replaced with `\+ memberchk(X,Lst)`.) – Fred Foo Jan 25 '12 at 17:41
  • @larsmans why `memberchk/2` instead of `member/2` in a `(\+)/1` call ? It'll stop at the first match anyway, won't it ? – m09 Jan 25 '12 at 17:50
  • @Mog: I believe `memberchk` is still more efficient; SWI-Prolog's `time/1` shows it performs fewer inferences. – Fred Foo Jan 25 '12 at 18:19
1

is_set/1: http://www.swi-prolog.org/pldoc/doc_for?object=is_set/1
(Assuming that you still want it for swi-prolog)

Thanos Tintinidis
  • 5,828
  • 1
  • 20
  • 31
0

alternatively, some forall madness (quadratic complexity so it's worse than the is_set/1 mentionned above, but w/e) :

duplicates(List) :-
    forall(select(Item, List, Rest),
           forall(member(Item2, Rest),
                  Item \== Item2)).
m09
  • 7,490
  • 3
  • 31
  • 58