Search code examples
filterprolog

How to filter a list in Prolog with multiple conditions?


I can filter a list based on 1 condition with the following steps:

  • building the list with findall
  • using include to isolate the elements that respect the condition.

But how to proceed when there is multiple condition to respect? In this particular case, the number of condition and the conditions themselves are unknown, so the predicate has to be applicable to all contexts.

Here is an example of how the condition are build and how they are called:

goal(drinkingAge,(person(_,_,A))) :- person(_,_,A), A > 21. %has at least 21 years old

And to call them on a person:

goal(drinkingAge,(person("Tom",male,24))).

returns true

For now I can filter a list with 1 condition with the following:

findall(person(A,B,C),person(A,B,C),L).
include(goal(drinkingAge), L, ReturnList).

Solution

  • You don't say how "all contexts" are represented but, as include/3 takes a generic goal of 1 argument as first argument (more precisely, the name of that goal), you can structure that goal accordingly.

    For example, if you need to fulfill a series of 1-argument goals held in a list then:

    succeed_all_goals_for_value([],_).
    succeed_all_goals_for_value([Goal|Goals],Value) :-
       call(Goal,Value),                         
       succeed_all_goals_for_value(Goals,Value). 
    

    Then include/3 can be called. For example, suppose number/1, odd/1 and notprime/1 are defined:

    ?- include(
           succeed_all_goals_for_value([number,odd,notprime]), 
           List,
           ListFiltered).
    

    With the more concrete goal/2 syntax

    We can become more concrete now. Here is a possibility, complete with plunit test cases:

    % ---
    % Helper
    % succeed_all_goals(+Goals,+Element). 
    % ---
    
    % Trivially succeed if there are no goals
    
    succeed_all_goals([],_). 
    
    % Otherwise go through the goals and succeed each one!
    % There are many ways of doing this...
    
    succeed_all_goals([GoalName|GoalNames],Element) :-
       goal(GoalName,Element),
       succeed_all_goals(GoalNames,Element).
    
    % ---
    % Call include/3 with a filtering goal
    % that checks the conjunction of all conditions
    % ---
    
    myfilter(GoalNames,List,ListFiltered) :-   
       include(
          succeed_all_goals(GoalNames),
          List,
          ListFiltered).
          
    % ---
    % A bit of testing
    % ---
    
    % Succeed if second argument is a person who has reached drinking age
    goal(drinkingAge, person(_,_,A)) :- A > 21.
    
    % Succeed if second argument is a person who is male
    goal(male, person(_,male,_)).
    
    :- begin_tests(myfilter).
    
    test(1) :- 
       myfilter(
          [drinkingAge,male],
          [person(jean,male,22),person(françoise,female,17)],
          ListFiltered),
       assertion(ListFiltered==[person(jean,male,22)]).
       
    test(2) :- 
       myfilter(
          [male],
          [person(jean,male,22),person(françoise,female,17)],
          ListFiltered),
       assertion(ListFiltered==[person(jean,male,22)]).
    
    :- end_tests(myfilter).
    

    And so:

    ?- [greatfilter].
    true.
    
    ?- run_tests.
    % PL-Unit: myfilter .. done
    % All 2 tests passed
    true.