Search code examples
prolog

Find a list of people who participate in more than one circle


I'm just starting to work with Prolog, so I don't really understand it. I have the facts:

circle(сhess, abbot).
circle(сhess, hannigan).
circle(сhess, abrams).
circle(crystal_voice, blake).
circle(crystal_voice, weller).
circle(crystal_voice, huxley).
circle(local_studies, barnes).
circle(local_studies, haskins).
circle(local_studies, abrams).
circle(local_studies, aberdeen).
circle(art, barnes).
circle(art, abbot).
circle(art, blake).

person(abbot, male, 20).
person(aberdeen, female, 18).
person(weller, male, 22).
person(abrams, female, 25).
person(adams, male, 21).
person(bond, female, 12).
person(haskins, male, 15).
person(blake, female, 20).
person(barnes, male, 20).
person(hannigan, female, 15).
person(huxley, male, 18).

I need to solve the problem: Find a list of people who participate in more than one circle.

I have a code that finds only the number of circles that a person visits.

count(Name, Count):- 
   findall(1, circle(_, Name), List),
   length(List, Count). 

Solution

  • The beginner way is:

    circles2(Person) :-
        circle(Circle1, Person),
        dif(Circle1, Circle2),
        circle(Circle2, Person).
        
    circles2_people(People) :-
        setof(Person, circles2(Person), People).
    

    ... which produces the right answer:

    ?- circles2_people(People).
    People = [abbot, abrams, barnes, blake].
    

    ... but is a little inefficient, due to circles2 producing duplicates which setof then removes:

    ?- bagof(Person, circles2(Person), People).
    People = [abbot, abrams, blake, barnes, abrams, barnes, abbot, blake].
    

    So, can use as an optimization:

    person_circles2(Person) :-
        person(Person, _, _),
        once(circles2(Person)).
    

    The above code "loops" through the persons (due to backtracking), and tries at most once for each person to satisfy the circles2 constraint - which does not duplicate the people:

    ?- bagof(Person, person_circles2(Person), People).
    People = [abbot, abrams, blake, barnes].
    

    However, one would still normally use setof instead of bagof, with the benefit of this optimization, to also be clear on intention:

    ?- setof(Person, person_circles2(Person), People).
    People = [abbot, abrams, barnes, blake].