Prolog: How would I seperate a list into two lists depending on the category?

Viewed 80

My query would be the following:

separate([eat(chips),drink(water),eat(burger),eat(banana),drink(coke)],food,drink).
food = [eat(chips),eat(burger),eat(banana)]
drink = [drink(water),drink(coke)]

I want to separate the list but I've not been able to figure out how to.

separate(X,Cat1,Cat2):-
     [Cat1|Cat2] = X,
     Cat2 = X,
     separate(X,Cat1,Cat2).

Currently I've been only able to use recursion to go through each element of the list but I don't really have any idea on how to start separating them into separate lists.

3 Answers

You could use the higher-order filter operations taking a Goal for filtering:

For example (note that variables have to start with uppercase letters in Prolog):

separate(TaggedList,Food,Drink) :-
   include(isFood,TaggedList,Food),   % isFood/1 will be called for each element
   include(isDrink,TaggedList,Drink). % same as above

isFood(eat(_)).   % no need to be complex; just succeed if argument matches
isDrink(drink(_)).  % same as above

And so:

?- separate(
   [eat(chips),drink(water),eat(burger),eat(banana),drink(coke)],
   Food,Drink).
Food = [eat(chips), eat(burger), eat(banana)],
Drink = [drink(water), drink(coke)].

Pattern matching. You want pattern matching.

When the list of food and drink is empty you have an easy base predicate:

separate([],[],[]).

When you want to separate out the food and drink it is as easy as this:

separate([eat(X)|T],[eat(X)|F],D) :- separate(T,F,D).
separate([drink(X)|T],F,[drink(X)|D]) :- separate(T,F,D).

When the head of the list matches eat then put the element on the first place of the Food list. When drink then on the Drink list. Simple.

When I run that:

?- separate([eat(chips),drink(water),eat(burger),eat(banana),drink(coke)],Food,Drink).
Food = [eat(chips), eat(burger), eat(banana)],
Drink = [drink(water), drink(coke)].

Both answers are good as they are. Another option for SWI-Prolog would be to use partition/4 like this:

separate(List, Eats, Drinks) :-
    partition(is_eat, List, Eats, Drinks).

is_eat(eat(_)).

Extra credit: why does this not work?

?- partition(eat(_),
             [eat(chips),drink(water),eat(burger),eat(banana),drink(coke)],
             Eats, Drinks).
Related