Search code examples
listdebuggingprologaccumulator

Removing given value from list and storing in accumulator


So far I have:

remove(_,[],R,R).

remove(X,[H|T],[],R) :- X =\= H, remove(X,T,[_|H],R).

But I can't quite work out why it's not working. The first is the base case when the list has been exhausted, and the accumulator is copied to R.

The second defines that the head must not equal X, and then recurse into the tail and add the Head to the accumulator.


Solution

  • First of all, =\= is disequality of the values of arithmetic expressions. It is not a general "unequal" predicate. For example, this doesn't work well:

    ?- a =\= b.
    ERROR: =\=/2: Arithmetic: `a/0' is not a function
    

    So your predicate has no hope of working for anything that is not a list of numbers or other arithmetic expressions. If your Prolog system has dif/2, use that:

    ?- dif(a, b).
    true.
    

    Only if the above doesn't work, use \= for disequality:

    ?- a \= b.
    true.
    

    (The difference between these two is that dif handles variables correctly, while \= doesn't.)

    Now, one immediate problem in your code is that you pattern match a list as [H|T], then try to construct a new list [_|H] for the recursive call. This raises a red flag: if H is the head of the first list, then in general it will not be a list itself. But non-lists should not appear as the tail in the list constructor. For example, if we match the list [1, 2, 3]:

    ?- [H|T] = [1, 2, 3].
    H = 1,
    T = [2, 3].
    

    ... and then try to construct a new list with this H as its tail:

    ?- [H|T] = [1, 2, 3], Xs = [42 | H].
    H = 1,
    T = [2, 3],
    Xs = [42|1].
    

    ... the result will not be a list:

    ?- [H|T] = [1, 2, 3], Xs = [42 | H], is_list(Xs).
    false.
    

    You probably want to have H as the head of the new accumulator, like so:

    remove(X, [H|T], R0, R) :-
        dif(X, H),
        remove(X, T, [H|R0], R).
    

    Another thing I changed from your second clause is that the accumulator R0 is not required to be [], as in your definition. In your definition, the recursive call would fail immediately since the new, non-empty accumulator would not unify with [].

    This already works for some cases:

    ?- remove(a, [b, c, d], [], Result).
    Result = [d, c, b] ;
    false.
    

    But not for others:

    ?- remove(a, [a], [], Result).
    false.
    

    You only have two clauses so far: One for the empty list (which doesn't apply here) and another for the case when the element to be removed is not equal to the head of the list (which doesn't apply here either!). You need a third clause for the case where the head of the list is the element to be removed:

    remove(X, [X|T], R0, R) :-
       ... %  fill in here
    

    Edit: As pointed out in the comments, the solution sketched above reverses the list of elements in the result list. This is usual for accumulator-based recursive list predicates: later list elements become earlier accumulator elements.

    However, an accumulator isn't needed here. The result can be constructed directly. Here is an implementation:

    remove(_X, [], []).
    remove(X, [Y|Xs], [Y|Ys]) :-
        dif(X, Y),
        remove(X, Xs, Ys).
    remove(X, [X|Xs], Ys) :-
        remove(X, Xs, Ys).
    

    This works for both positive and negative test cases:

    ?- remove(a, [b, a, c], Result).
    Result = [b, c] ;
    false.
    
    ?- remove(a, [b, c, d], Result).
    Result = [b, c, d] ;
    false.
    

    It works in the general case:

    ?- remove(X, Xs, Ys).
    Xs = Ys, Ys = [] ;
    Xs = Ys, Ys = [_G4794505],
    dif(X, _G4794505) ;
    Xs = Ys, Ys = [_G4794601, _G4794604],
    dif(X, _G4794604),
    dif(X, _G4794601) ;
    Xs = Ys, Ys = [_G4794697, _G4794700, _G4794703],
    dif(X, _G4794703),
    dif(X, _G4794700),
    dif(X, _G4794697) ;
    Xs = Ys, Ys = [_G4794793, _G4794796, _G4794799, _G4794802],
    dif(X, _G4794802),
    dif(X, _G4794799),
    dif(X, _G4794796),
    dif(X, _G4794793) ;
    Xs = Ys, Ys = [_G4794889, _G4794892, _G4794895, _G4794898, _G4794901],
    dif(X, _G4794901),
    dif(X, _G4794898),
    dif(X, _G4794895),
    dif(X, _G4794892),
    dif(X, _G4794889) ;
    Xs = Ys, Ys = [_G4794985, _G4794988, _G4794991, _G4794994, _G4794997, _G4795000],
    dif(X, _G4795000),
    dif(X, _G4794997),
    dif(X, _G4794994),
    dif(X, _G4794991),
    dif(X, _G4794988),
    dif(X, _G4794985) .
    

    But the enumeration above is not fair! It only exercises the second clause, not the third one. We can get fair enumeration by grouping by the length of Xs:

    ?- length(Xs, N), remove(X, Xs, Ys).
    Xs = Ys, Ys = [],
    N = 0 ;
    Xs = Ys, Ys = [_G4794582],
    N = 1,
    dif(X, _G4794582) ;
    Xs = [X],
    N = 1,
    Ys = [] ;
    Xs = Ys, Ys = [_G4794678, _G4794681],
    N = 2,
    dif(X, _G4794678),
    dif(X, _G4794681) ;
    Xs = [_G4794585, X],
    N = 2,
    Ys = [_G4794585],
    dif(X, _G4794585) ;
    Xs = [X, _G4794588],
    N = 2,
    Ys = [_G4794588],
    dif(X, _G4794588) ;
    Xs = [X, X],
    N = 2,
    Ys = [] ;
    Xs = Ys, Ys = [_G4794774, _G4794777, _G4794780],
    N = 3,
    dif(X, _G4794774),
    dif(X, _G4794780),
    dif(X, _G4794777) .
    

    We can also use this "backwards" in a limited way: By leaving the second argument Xs free and specifying a concrete list for the third argument Ys, we get a predicate inserting the given element into Ys in all the possible places, giving Xs:

    ?- remove(a, Xs, [b, c, d]).
    Xs = [b, c, d] ;
    Xs = [b, c, d, a] ;
    Xs = [b, c, d, a, a] ;
    Xs = [b, c, d, a, a, a] .
    

    Again this is not fair, but again that can be fixed:

    ?- length(Xs, N), remove(a, Xs, [b, c, d]).
    Xs = [b, c, d],
    N = 3 ;
    Xs = [b, c, d, a],
    N = 4 ;
    Xs = [b, c, a, d],
    N = 4 ;
    Xs = [b, a, c, d],
    N = 4 ;
    Xs = [a, b, c, d],
    N = 4 ;
    Xs = [b, c, d, a, a],
    N = 5 .  % etc.