Generically speaking, it sounds like you have two collections of facts and you just want to define a rule that succeeds for each unique matching pair.
The sets of facts are person
and food
. As I mentioned in the comment, defining the foods as a single fact with a list will be a bit awkward. I would define food
in the same way you are defining person
, that is, as individual facts.
person(john, 36).
person(jane, 3).
person(amber, 32).
person(emmy, 2).
person(clement, 37).
person(patrick, 15).
person(emilie, 20).
food(frechfries, _).
food(apple, good).
food(burger,_).
food(kiwi, good).
food(banana, good).
food(potato, good).
food(orange, good).
food(cereal, good).
food(hotdog, _).
food(steak, _).
food(coca, _).
food(water, good).
Now we can think about defining a match-up. You can think of it this way:
A successful match-up consists of a list of person-food pairs in which the each person and each food only appear once (are uniquely selected from the respective collections) and each person is represented.
Since we're trying to select each person and food uniquely, I'm thinking select/3
would be a good choice as a core mechanism to achieve the desired goal.
person_food_pairs(PeopleFoodPairings) :-
findall(Person, person(Person, _), People),
findall(Food, food(Food, _), Foods),
length(People, NumberOfPeople),
length(Foods, NumberOfFoods),
NumberOfPeople =< NumberOfFoods,
person_food_pairing(People, Foods, PeopleFoodPairings).
person_food_pairing([], _, []).
person_food_pairing(People, Foods, [Person-Food|RemainingPairs]) :-
select(Person, People, RemainingPeople),
select(Food, Foods, RemainingFoods),
person_food_pairing(RemainingPeople, RemainingFoods, RemainingPairs).
There are going to be a lot of combinations, so lots of solutions! I didn't see any other conditions you had which would limit this. Also, you could leave out this set of lines, and the code will still yield the same results due to the definition of person_food_pairing/2
:
length(People, NumberOfPeople),
length(Foods, NumberOfFoods),
NumberOfPeople =< NumberOfFoods,
However, then the code would do a lot of unnecessary execution just to finally determine it cannot do the pairing. So these lines help determine that case early.
If, as noted in the comments, you have conditions that involve some of the person and food attributes, you'll need to carry those attributes along until you do the pairing.
person_food_pairs(PeopleFoodPairings) :-
findall(Person-Age, person(Person, Age), People), % Include age
findall(Food-Health, food(Food, Health), Foods), % Include health factor
length(People, NumberOfPeople),
length(Foods, NumberOfFoods),
NumberOfPeople =< NumberOfFoods,
person_food_pairing(People, Foods, PeopleFoodPairings).
person_food_pairing([], _, []).
person_food_pairing(People, Foods, [Person-Food|RemainingPairs]) :-
select(Person-Age, People, RemainingPeople), % Select person-age pair
select(Food-Health, Foods, RemainingFoods), % Select food-health pair
(Age < 20 -> Health == good ; true), % Condition to include
person_food_pairing(RemainingPeople, RemainingFoods, RemainingPairs).
Note the use of ==/2
here and not unification =/2
. The reason for this is that you want _
not to match good
. If you use unification, then Prolog will successfully unify the variable _
with the atom good
. ==/2
, on the other hand, checks if these two terms are truly the same and does not do unification.