Equality of sets

Viewed 239

Can anyone help me with the following task: I need to define a predicate eq_set, which succeeds if the sets S1 and S2 are equal when it comes to the number of their elements.

But it works only if they are exactly the same number and order. I want to create a code that shows all varieties and doesn't take order into account. Can you help me,please?

I wrote:

eq_set([],[]).
eq_set([H|T],[H|T1]) :-
    eq_set(T,T1).

But it works only if they are exactly the same number and order. I want to create a code that shows all varieties and doesn't take order into account.

The closest translation I have of the assignment, which is in Bulgarian, is: "Define the predicate eq_set, which succeeds if the sets (S1, S2) coincide.

4 Answers

It really really depends on how the predicate is going to be used.

Assuming that a "set" is indeed a Prolog list without duplicates but not in any particular order; then two sets in that presentation "coincide" if they are permutations of each other. In other words, it would be enough to define eq_set/2 as:

eq_set(A, B) :-
    my_permutation(A, B).

and just use the textbook definition of permutation/2 which uses the textbook definition of select/3 (See "The Art of Prolog (Second Edition)" by Sterling and Shapiro, pp 67-9):

my_permutation([], []).
my_permutation(Xs, [Y|Ys]) :-
    my_select(Y, Xs, Xs0),
    my_permutation(Xs0, Ys).

my_select(X, [X|Xs], Xs).
my_select(X, [Y|Ys], [Y|Zs]) :-
    my_select(X, Ys, Zs).

(I renamed those just to make sure I am not using the standard library definitions; SWI-Prolog has both select/3 and permutation/2 in the autoloaded library(lists); the definitions are basically the same, but they do some run-time type-checking on the arguments.)

Here is how you can use it:

?- eq_set([1,2,3], [2,3,1]).
true ;
false.

?- eq_set([1,2,3], S).
S = [1, 2, 3] ;
S = [1, 3, 2] ;
S = [2, 1, 3] ;
S = [2, 3, 1] ;
S = [3, 1, 2] ;
S = [3, 2, 1] ;
false.

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

?- eq_set(A, B).
A = B, B = [] ;
A = B, B = [_4480] ;
A = B, B = [_4480, _4492] ;
...

I am not sure how useful the last query is. You can force it to enumerate solutions in order of increasing size of the "set", like this:

?- length(S1, _), eq_set(S1, S2), numbervars(S1).
S1 = S2, S2 = [] ;
S1 = S2, S2 = [A] ;
S1 = S2, S2 = [A, B] ;
S1 = [A, B],
S2 = [B, A] ;
S1 = S2, S2 = [A, B, C] ;
S1 = [A, B, C],
S2 = [A, C, B] ;
S1 = [A, B, C],
S2 = [B, A, C] ;
S1 = [A, B, C],
S2 = [B, C, A] ;
S1 = [A, B, C],
S2 = [C, A, B] ;
S1 = [A, B, C],
S2 = [C, B, A] ;
S1 = S2, S2 = [A, B, C, D] .

(Don't worry about the numbervars, it is just there to give readable names to all the free variables in the sets. Keep in mind that unifying two free variables makes them the same variable.)

This is a starting point, but maybe it is already good enough. The most glaring omission is that it doesn't require the arguments to be lists without duplicates. One way to define this would be to require that each element is different from all other elements. Since "is different" is commutative, you can define it like this:

is_set([]).
is_set([X|Xs]) :-
    all_different(Xs, X),
    is_set(Xs).

all_different([], _).
all_different([Y|Ys], X) :-
    dif(X, Y),
    all_different(Ys, X).

This uses dif/2 which is a widely available predicate (but does your Prolog have it?).

We would have maybe used maplist for that last one:

is_set([]).
is_set([X|Xs]) :-
    maplist(dif(X), Xs).
    is_set(Xs).

You call them "sets" but the data structure you are using is a list. It is easiest to just sort the two lists:

eq_set(A, B) :-
    % prerequisites: A and B are lists without duplicates
    sort(A, S),
    sort(B, S).

If you want something more complicated (for some reason) you need to be more specific.

With this definition:

?- eq_set([a,b,c], [a,b]).
false. % OK

?- eq_set([a,b,c], [a,b,c]).
true. % OK

?- eq_set([a,c,b], [a,b,c]).
true. % OK

?- eq_set([a,a,b], [a,b,b]).
true. % Not sure....

You are pretty close in your solution.

We have two cases 1) The first list argument is bigger 2) The second list argument is bigger

If you already know which one is bigger, you can just do

%in case the left one is longer
eq_set_left(_,[]).
eq_set_left(X,[H|T]):-member(H,X), eq_set_left(X,T).

So a very simple solution and yet very optimal could be this:

%in case the right one is longer
eq_set_right([],_).
eq_set_right([H|T], X):- member(H,X), eq_set_right(T,X).

%in case the left one is longer
eq_set_left(_,[]).
eq_set_left(X,[H|T]):-member(H,X), eq_set_left(X,T).

%both cases, equal length is also included here
eq_set(X,Y):- eq_set_left(X,Y).
eq_set(X,Y):- eq_set_right(X,Y).

eq_set(X, Y) is true if either X is subset of Y, Y is subset of X or they are equal

A set is defined as a collection of distinct things where order is not important, which is to say that sets {a,b,c} and {b,a,c} are identical.

From that, one could say that two sets are identical if neither set contains an element that is not also found in the other (or conversely, two sets are not identical if either set contains an element not found in the other.

From that, one could simply say:

eq_set(Xs,Ys) :-
   findall( (Xs,Ys) , ( member(X,Xs), \+ member(X,Ys) ), [] ),
   findall( (Xs,Ys) , ( member(Y,Ys), \+ member(Y,Xs) ), [] )
   .

Or if you don't want to use the built-in findall/3,

eq_set(Xs,Ys) :-
  a_not_in_b( Xs , Ys , [] ) ,
  a_not_in_b( Ys , Xs , [] ) .

a_not_in_b( []     , [] , [] ) .
a_not_in_b( [A|As] , Bs , Xs ) :- member(A,Bs) , a_not_in_b( As, Bs,    Xs  ) .
a_not_in_b( [A|As] , Bs , Xs ) :-                a_not_in_b( As, Bs, [A|Xs] ) .

One should note that both of these has roughly O(N2) performance. If the sets in question are large, you might want to first sort each set and then merge the two sorted lists to identify those elements that are not common to both sets:

eq_set(Xs,Ys) :-
  sort(Xs,X1),
  sort(Ys,Y1),
  X1 == Y1.
Related