Reputation: 527
So I have the following predicate which takes a list of symbols as its first parameter, then outputs this list with all of the duplicates removed:
removeDups([], []).
removeDups([H|T], [H|T1]) :-
subtract(T, [H], T2),
removeDups(T2, T1).
It works as intended but I'm not exactly sure how to make sense of it.
What I've understood so far is:
T2
?T2
is stored into T1
?I'm quite confused as to what exactly is going on here so any help would be really appreciated!
Upvotes: 1
Views: 79
Reputation: 8140
The predicate subtract/3 describes a relation where the 3rd argument is the 1st argument without the elements of the 2nd argument, e.g.:
?- subtract([1,2,3],[1],L).
L = [2,3]
?- subtract([1,2,3],[1,2],L).
L = [3]
The first rule of removeDups/2 is the base case:
removeDups([],[]).
You can read it as: The empty list without duplicates is the empty list. The second rule is the general case of the relation, where the 1st list is not empty.
removeDups([H|T], [H|T1]) :- % the head of list 1 is in the head of list 2
subtract(T, [H], T2), % T2 is the tail of list 1 without H
removeDups(T2,T1). % T1 is a duplicate free T2
So the goal with subtract/3 describes a list T2
that contains all elements but H
of T
. Since through the recursions the list T2
becomes shorter and shorter you inevitably end up with the base case []
. And that is described by the first rule. Procedurally speaking you need the base case so your predicate can terminate. However, as pointed out by @repeat, the use of subtract/3 can be problematic if the first two arguments contain free variables. Consider the following examples:
?- X=1, subtract([1,2,3],[X],L).
L = [2,3],
X = 1
?- subtract([1,2,3],[X],L), X=1.
L = [2,3],
X = 1
But:
?- X=2, subtract([1,2,3],[X],L).
L = [1,3],
X = 2
?- subtract([1,2,3],[X],L), X=2.
no
So it's a good idea to test those arguments for sufficient instantiation as suggested by him in the comments:
removeDups([],[]).
removeDups([H|T], [H|T1]) :-
safe_subtract(T, [H], T2),
removeDups(T2,T1).
safe_subtract(As,Bs,Xs) :-
( ground(As+Bs) ->
subtract(As,Bs,Xs) ;
throw(error(instantiation_error,_))).
Upvotes: 2
Reputation: 22585
This procedure states that removeDups(L,L1)
:
L
and L1
are both lists with at least one element, the head of each list (H
) unifies with the head of the other list, then subtract/3 is called with the tail of the list (T
) from the first argument and a list comprising solely with the head. This procedure will unify the third argument (T2
) with a list containing the elements of T
without H
. Then the recursive call to removeDups/3
applies the same algorithm to the resulting list (which does not have H
). The recursive call has at least one element less than the original list L
. Upon returning from the recursive call T1 should have no duplicates (and H
won't be in it either). However as the third argument to removeDups is [H|T1]
the caller will have in the third argument H
as the head of the third list.Upvotes: 1