Trying to write a procedure that given a value and a list, it deletes all the occurence of that value in the list a wrote:
delMember(X, [], []) :- !.
delMember(X, [X|Xs], Y) :- !, delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- !, delMember(X, Xs, Y2), append([T], Y2, Y).
Since the cut
this code cannot answer correctly queries like:
delMember(Y, [1,2,3,1,2,3,1,2,3], [1, 2, 1, 2, 1, 2 ]).
If I delete the cuts:
delMember(X, [], []).
delMember(X, [X|Xs], Y) :- delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- delMember(X, Xs, Y2), append([T], Y2, Y).
it fail in queries like:
delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,3,1,2,3,1,2,3]).
(returns true
, when the correct answer is false
).
How can I make it works in both situations?
Maybe I can check that X is not T
in the third line of code, I tried:
delMember(X, [T|Xs], Y) :- not(X = T), delMember(X, Xs, Y2), append([T], Y2, Y).
but it does not work.
Use of the cut
Here, you can see that you use
!/0
in the last clause of your predicate. It's not necessary. After the last clause, there's no choice left (Prolog remembers choice points from left to right and top to bottom), so a cut (that removes choices), won't do anything useful since you're already at the bottom of your list of choices.To illustrate, see
Here, to prove
a
, Prolog will first tryb
, thenc
, thend
(left to right, then top to bottom).BTW, as a beginner in Prolog, you should go as far as totally avoid the use of the cut. It will just add to your misunderstandings as long as you don't get recursion and the other basics of logic programming.
Prolog recursion
That little note aside, your problem is that you've not properly understood Prolog recursion yet. Please see the first part of this answer that already adresses this concern.
Your third clause is wrong:
it should read:
Well, it's not really wrong, it's just really suboptimal. It's not tail-recursive and uses
append/3
, which would turn your linear predicate into a quadratic one. Plus, as you noticed, since it's not tail-recursive, termination is tougher to obtain in some cases.Then, to remove the use of the cut
!/0
, you can consider adding a guard to the last clause:The guard,
dif(X, T)
, specifies that if we're in the third case we cannot be in the second at the same time :X
cannot be unified withT
here.Note, there's still one way we can't use the predicate, this is,
+, -, +
, as cTI tells us. So queries like?- delMember(1, R, [2, 3]).
will loop with my version.I hope it has been useful.
Lets do a bit of rephrasing: since you want to use the predicate in more than one instantiation pattern " a procedure that given a value and a list, it deletes all the occurrence of that value in the list" does not define how it should behave in the other cases. So, we probably want something like "the predicate is true if the second and third argument are lists L1, L2 and L1 is the same list with L2 if we ignore all occurrences of the first argument"
Now, there are two ways of writing a predicate with multiple possible instantiations; you can use meta-logical predicates such as
var/1
andground/1
and write code for each one (which will probably allow you to write code that's optimised for that specific instantiation) or write code that would define the properties logically (which can be more challenging).In this case, we could do something like that:
which has the following behaviour:
regarding the last call; prolog returns a list of X that grows cause a depth-first algorithm is used; by using
length/2
we can get the results breadth-first (_G means that the variable is not instantiated (it can be anything)):edit: as @chac pointed out, the predicate above behaves incorrectly if the first list has (at least) one duplicate element:
this is because
\==/2
and\=/2
do not actually impose a restriction on the variable. this could be solved by switching the order of the rules in the third clause:this however means that the predicate is no longer tail-recursive. to fix that we could keep a list of the values that X shouldn't be:
however, according to the following, the tail recursive version is actually slower:
still, the + - + doesnt work (it falls in a infinite loop). but why? the problem lies in the order of the clauses: del(1, L1, [2]) will first apply the rule that "adds" X to the head of L1, then applies the same rule forever. this can be countered by using (again)
length/2
:alternatively we can change the order of the clauses:
yet
length/2
might be useful again since without it prolog does a depth first search:of course
length/2
can be incorporated in a wrapper predicate since it doesnt affect the other instantiation patterns.This is not a true answer, just an extended note to Mog and thanosQR answers, too long to fit in a comment. Such answers are agreeable and instructive, but the cut removal need to be rethinked. Consider:
This definition allows
that fails in original Mog' code because of the guard in last cause. It's worth to note that replacing there the guard with
X \== T
(that restricts the test to matched instantiation state), also solve this query, as noted by thanosQR.But none of these snippets solve the general case:
Here goes a snippet that works also with the first and third arguments uninstantiated:
I will explain here what this code does. The idea is to:
Step 1 is done with
setof(X, member(X, Y), L)
and it works in two ways. When parameterX
is already instantiated thenL
will be either the list[X]
ifX
is contained in input parameterY
, of it will fail ifX
is not contained inY
. On the other hand, ifX
was uninstantiated thenL
will be the set of distinct elements of input parameterY
.Now in step 2 we backtrack over every element of
L
, and for each member of this list we filter this element from the input listY
which yields the result. We collect all this elements in output listZ
.Note that when parameter X was uninstantiated when the procedure wass called, upon backtracking of
member(X, Y)
we will get every member of input listY
that will be used for filtering.Test cases: