Search code examples
listduplicatesprologlogicpredicate

Removing Duplicates while maintaining order in Prolog


I am trying to create a predicate that removes duplicates from a list while maintaining its relative order. For example a list that is [1,2,2,3,4,5,5,2] should return [1,2,3,4,5]. However, my code is only able to remove consecutive duplicates and for instance, not the 2 at the end.

remove_duplicates([],[]).
remove_duplicates([H],[H]).
remove_duplicates([H,H|T],[H|List]) :- remove_duplicates(T,List).
remove_duplicates([H,X|T],[H|T1]):- X\=H, remove_duplicates([X|T],T1).

Another approach I was thinking of was to use member to see if the Tail contains the Head. However, the only way I can think of solving it that way is where I would remove the head, if head is a member of tail. This would however keep the last instance of the number only and break the relative order of the numbers in the list.

For instance:

[1,2,2,3,4,5,5,2] 
[1,3,4,5,2]

When I actually want

[1,2,3,4,5]

Solution

  • You can make use of an accumulator: an extra parameter, here a list that is initially empty and when new elements arise will grow. Each recursive call the list is passed (or an updated copy).

    For example:

    remove_duplicates(LA, LB) :-
        remove_duplicates(LA, LB, []).
    
    remove_duplicates([], [], _).
    remove_duplicates([H|T], R, Seen) :-
        (  member(H, Seen)
        -> (R = S, Seen1 = Seen)
        ;  (R = [H|S], Seen1 = [H|Seen])
        ),
        remove_duplicates(T, S, Seen1).

    This then gives us:

    ?- remove_duplicates([1,2,2,3,4,5,5,2], L).
    L = [1, 2, 3, 4, 5].
    

    Of course you can make use of more effective datastructures than a list. I leave that as an exercise.