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.
delMember(X, [], []) :- !.
delMember(X, [X|Xs], Y) :- !, delMember(X, Xs, Y).
delMember(X, [T|Xs], Y) :- !, delMember(X, Xs, Y2), append([T], Y2, Y).
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
a :- b; c.
a :- d.
Here, to prove a
, Prolog will first try b
, then c
, then d
(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.
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:
delMember(X, [T|Xs], Y) :- delMember(X, Xs, Y2), append([T], Y2, Y).
it should read:
delMember(X, [T|Xs], [T|Y]) :- delMember(X, Xs, Y).
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:
delMember(_, [], []).
delMember(X, [X|Xs], Y) :-
delMember(X, Xs, Y).
delMember(X, [T|Xs], [T|Y]) :-
dif(X, T),
delMember(X, Xs, Y).
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 with T
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.
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:
delMember(_, [], []).
delMember(X, [X|Xs], Y) :-
delMember(X, Xs, Y), !.
delMember(X, [T|Xs], [T|Y]) :-
delMember(X, Xs, Y).
This definition allows
?- delMember(Y, [1,2,3,1,2,3,1,2,3], [1,2,1,2,1,2]).
Y = 3.
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:
?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
X = 1,
Y = [1, 2] ;
Y = [1, 2, 1].
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
and ground/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:
del(_, [], []).
del(X, [X|L1], L2):-
del(X,L1,L2).
del(X, [H|L1], [H|L2]):-
X\==H,
del(X,L1,L2).
which has the following behaviour:
19 ?- del(1, [1,2,3], X).
X = [2, 3] ;
false.
1,2,3,
20 ?- del(1, [1,2,3], [2,3]).
true ;
false.
21 ?- del(X, [1,2,3], [2,3]).
X = 1 ;
false.
22 ?- del(X, [1,2,3], Y).
X = 1,
Y = [2, 3] ;
X = 2,
Y = [1, 3] ;
X = 3,
Y = [1, 2] ;
Y = [1, 2, 3] ;
false.
23 ?- del(X, P, Y).
P = Y, Y = [] ;
P = [X],
Y = [] ;
P = [X, X],
Y = [] ;
P = [X, X, X],
Y = [] ;
P = [X, X, X, X],
Y = [] ;
P = [X, X, X, X, X],
Y = [] ;
P = [X, X, X, X, X, X],
Y = [] .
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)):
24 ?- length(P,N), del(X, P, Y).
P = [],
N = 0,
Y = [] ;
P = [X],
N = 1,
Y = [] ;
P = [_G548],
N = 1,
Y = [_G548] ;
P = [X, X],
N = 2,
Y = [] ;
P = [X, _G551],
N = 2,
Y = [_G551] ;
P = [_G548, X],
N = 2,
Y = [_G548] ;
P = [_G548, _G551],
N = 2,
Y = [_G548, _G551] ;
P = [X, X, X],
edit: as @chac pointed out, the predicate above behaves incorrectly if the first list has (at least) one duplicate element:
?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
X = 1,
Y = [1, 2] ; <----- wrong
Y = [1, 2, 1].
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:
del(_, [], []).
del(X, [X|L1], L2):-
del(X,L1,L2).
del(X, [H|L1], [H|L2]):-
del(X,L1,L2),
X\==H.
4 ?- del(X,[1,2,1],Y).
X = 1,
Y = [2] ;
X = 2,
Y = [1, 1] ;
Y = [1, 2, 1] ;
false.
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:
del(X,L1,L2):-
del(X,L1,L2,[]).
del(X, [], [], NotX):-
\+ member(X,NotX).
del(X, [X|L1], L2, NotX):-
del(X,L1,L2,NotX).
del(X, [H|L1], [H|L2], NotX):-
X\==H, % <--- optional; stops the execution earlier (saving time)
del(X,L1,L2,[H|NotX]).
however, according to the following, the tail recursive version is actually slower:
?-time(forall((between(1,50,N),length(X,N),del2(1,X,[2,3,2,3])),true)).
% 25,600,793 inferences, 5.468 CPU in 5.548 seconds (99% CPU, 4682134 Lips)
true.
?- time(forall((between(1,50,N),length(X,N),del_tr(1,X,[2,3,2,3])),true)).
% 37,346,143 inferences, 6.426 CPU in 6.428 seconds (100% CPU, 5811563 Lips)
true.
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
:
?- length(X,2), del(1,X,[2]).
X = [1, 2] ;
X = [2, 1] ;
false.
alternatively we can change the order of the clauses:
del(_, [], []).
del(X, [H|L1], [H|L2]):-
X\==H,
del(X,L1,L2),
X\==H.
del(X, [X|L1], L2):-
del(X,L1,L2).
yet length/2
might be useful again since without it prolog does a depth first search:
?- del(1,X,[2]).
X = [2] ;
X = [2, 1] ;
X = [2, 1, 1] ;
X = [2, 1, 1, 1] ;
X = [2, 1, 1, 1, 1] ;
X = [2, 1, 1, 1, 1, 1] ;
X = [2, 1, 1, 1, 1, 1, 1]
of course length/2
can be incorporated in a wrapper predicate since it doesnt affect the other instantiation patterns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With