Search code examples
listprolog

Prolog predicates not working


I am completely confused with Prolog. I have two simple predicates that I am trying to figure out, but can't seem to get them right. First I am trying to figure out a variation of a delete predicate where I need to delete all occurrences of a given element from a List1, and see if it is equal to List2.

This is what I tried.

del(S,[P],[P]).
del(S,[S],[]).
del(S,[H1,H2|T],L2) :- H1 = S, del([H2|T],L2).
del(S,[H1,H2|T],[H1,T2]) :- H1 \= S, del([H2|T],T2).

The other predicate takes two lists and takes all occurrences of the element 2 in List1 and replaces it with 4 and sees if List1 is the same as List2. To make is simplier, there's no sublists.

These are what I have tried.

change([],[]).
change([H1,H2|T],L) :- H1 = 2, append([2],L), change([H2|T],L).
change([H1,H2|T],L) :- H1 \= 2, append(H1,L), change([H2|T],L).

I would like to know what errors I have made and perhaps an explanation of how these terms are suppose to work. Thank you.


Solution

  • Examination of del/3

    (1) del(S,[P],[P]).
    (2) del(S,[S],[]).
    (3) del(S,[H1,H2|T],L2) :- H1 = S, del([H2|T],L2).
    (4) del(S,[H1,H2|T],[H1,T2]) :- H1 \= S, del([H2|T],T2).
    

    (1) This rule says that the list [P] is the list [P] with the element S removed. The problem with this rule is that S and P could be the same value, and so the rule isn't always true. If you wanted this to be true all the time (be a valid rule), you'd need to stipulate that S and P can't be instantiated to the same value:

    del(S, [P], [P]) :- S \= P.
    

    That's assuming we even need this rule, but we'll leave it here for the time being as, in this new state, it's correct.

    (2) This rule says that [] is the list [S] with the element S removed. That is true.

    (3) This rule says that L2 is the list [H1,H2|T] with S removed if H1 and S are unified and L2 is [H2|T] with S removed. BUT, there's a missing argument. You have del([H2|T], L2) but the del/3 is defined to take 3 arguments. We'll assume you meant S is the first argument (S is still what's being removed), so del(S, [H2|T], L2). The newly repaired rule appears to be logical.

    (4) This rule says that *[H1,T2] is the list [H1,H2|T] with the element S removed if H1 is not unifiable with S and T2 is the list [H2|T] with the element S removed. This has the same problem as #3 with the missing argument to del/3 which we'll, again, assume is S, making it, del(S, [H2|T], T2). Another problem is that you have [H1,T2] which is a list of only two elements: H1 and T2. Another typo. This should be [H1|T2]. So the repaired rule seems to make sense now.

    Just fixing these couple of careless errors causes your predicate to almost work! It will yield the correct result when the first argument is instantiated:

    | ?- del(a, [a,b,c,a,d,a], L).
    
    L = [b,c,d] ? a
    
    no
    

    Also, it can be cleaned up a bit. The H2 isn't really used in the 3rd and 4th clauses. And in the 3rd clause you can instantiate S and H1 in the head of the predicate. So those two become:

    (3) del(S, [S|T], L2) :- del(S, T, L2).
    (4) del(S, [H|T], [H|T2]) :- H \= S, del(S, T, T2).
    

    The predicate fails on the list being empty. I'm not sure if this is intentional, but you could argue that del(X, [], []) should be true (an empty list results when you remove an element from the empty list). If we include this rule:

    (1a) del(_, [], []).
    

    We can now get rid of rules (1) and (2) because (3) and (4) will take care of them and recurse down to rule (1a).

    Additionally, the rules still fail on this case:

    | ?- del(X, [a,b,c], [a,c]).
    
    no
    

    It would be nice if this burped out, X = b. The problem is in clause (4) where we check H \= S which means H and S are not unifiable. This expression works against us if S is a variable because then H \= S will always fail (since S could indeed be unified to H). So we replace it with dif(H,S) to check if these terms are the same. Some Prologs don't offer dif/2, in which case, you could substitute \== for this solution (H \== S). Our resulting rule set is:

    (1) del(_, [], []).
    (2) del(S, [S|T], L2) :- del(S, T, L2).
    (3) del(S, [H|T], [H|T2]) :- dif(H, S), del(S, T, T2).
    

    Let's "reread" these rules:

    1. Any element removed from the empty list is the empty list.
    2. The list [S|T] with the element S removed is the list L2 if L2 is the list T with the element S removed.
    3. The list [H|T2] is the list [H|T] with the element S removed if S is different than H and T2 is the list T with the element S removed.

    That looks a lot simpler although just a couple of simple reworks away from the original. It will now produce this result, which is nice:

    | ?- del1(X, [a,b,c,b,d], [a,c,d]).
    
    X = b ? ;
    
    (1 ms) no
    

    Examination of change/2

    (1) change([],[]).
    (2) change([H1,H2|T],L) :- H1 = 2, append([2],L), change([H2|T],L).
    (3) change([H1,H2|T],L) :- H1 \= 2, append(H1,L), change([H2|T],L).
    

    (1) This rule says if we change all the 2's in the empty list, we get the empty list. Sounds good.

    (2) This rule says if I take the list [H1,H2|T] and I change the 2's to 4's, I get L if H1 is 2 and I append L to [2] (which will always fail since [2] is not variable - see the online docs for append/2 and append/3! append/2 is for appending lists of lists, for example) and L is [H2|T] with 2's changed to 4's. Already this isn't making any logical sense at all. If I'm changing 2's to 4's why am I appending something to [2]? We need to lose the append and simply unify L with [4|T2] where T2 is [H2|T] with its 2's changed to 4's. Or in other words:

    (2) change([H1,H2|T], L) :- H1 = 2, L = [4|T2], change([H2|T], T2).
    

    This can be simplified further using the method of incorporating the unification in the head of the clause as above. Also note, again, we have no real use for making H2 visible here. So [H2|T] can just be a T:

    (2) change([2|T], [4|T2]) :- change(T, T2).
    

    So we use a 4 instead of a 2 if there's a 2, then process the tails.

    (3) This rule has the same problem (2) does with the append/2 query. We can follow the same pattern of what we did with rule (2), and fix the non-equal check:

    (3) change([H|T], [H|T2]) :- H \== 2, change(T, T2).
    

    So we just carry over the element H if it's not a 2, then process the tails. The complete change predicate looks like:

    (1) change([], []).
    (2) change([2|T], [4|T2]) :- change(T, T2).
    (3) change([H|T], [H|T2]) :- H \== 2, change(T, T2).
    

    Rereading the rules, seeing that they make logical sense:

    1. Changing 2's to 4's in the empty list is the empty list.
    2. Changing 2's to 4's in the list [2|T] is the list [4|T2] if T2 is the list T with the 2's changed to 4's.
    3. Changing 2's to 4's in the list [H|T] is the list [H|T2] if H is not 2 and T2 is the list T with the 2's changed to 4's.