Newbie study of bagof

Hi, as the Prolog newbie I am, I stumbled in some tutorial (I don’t remember which one) with the following little knowledge base:

  age(harry,13).
  age(draco,14).
  age(ron,13).
  age(hermione,13).
  age(dumbledore,60).
  age(hagrid,30).

After some simple exercises with setof/3 and bagof/3, I tried to construct a goal whose answer would be a list containing sublists, each of which would include a non-duplicated age, and a list of characters with that age. Something that would look exactly as:

[[13,[harry,hermione,ron],[14,[draco]],[30,[hagrid]],[60,[dumbledore]]]

My best approximation, at this moment is:

setof([A,[Ch]], setof(A, age(Ch,A), B), All).
B = [13],
All = [[_1002486, [ron]], [_1002526, [hermione]], [_1002566, [harry]]] ;
B = [14],
All = [[_1002686, [draco]]] ;
B = [30],
All = [[_1002606, [hagrid]]] ;
B = [60],
All = [[_1002646, [dumbledore]]].

Or this one which is not very helpfull:

group_by(Age, AllAges, edad(Ch,Age), All).          
Age = 13,
All = [_1008502, _1008470, _1008454] ;
Age = 14,
All = [_1008486] ;
Age = 30,
All = [_1008422] ;
Age = 60,
All = [_1008438].

Can anyone point me to a way to obtain the desired result?
Thanks in advance.

$ cat b.pl
age(harry,13).
age(draco,14).
age(ron,13).
age(hermione,13).
age(dumbledore,60).
age(hagrid,30).

t(AgePeople) :-
    setof(Age-People,
          setof(Person, age(Person, Age), People),
          AgePeople).

$ swipl b.pl
?- t(AgePeople).
AgePeople = [13-[harry, hermione, ron], 14-[draco], 30-[hagrid], 60-[dumbledore]].

I used pairs with “-” because that allows using the result with the library(pairs) predicates.

In the inner setof/3 (setof(Person, age(Person, Age), People)), Age is “free” in the goal age(Person,Age). This allows backtracking over all the possible values of Age in the outer setof/3.

If you have trouble with this, try an alternative way of writing:

t2(AgePeople) :- setof(Age-People, age_people(Age, People), AgePeople).

age_people(Age, People) :- setof(Person, age(Person, Age), People).
?- age_people(Age, People).
Age = 13,
People = [harry, hermione, ron] ;
Age = 14,
People = [draco] ;
Age = 30,
People = [hagrid] ;
Age = 60,
People = [dumbledore].
1 Like

I would do the same way as Peter, but just to point out group_pairs_by_key/2…

?- setof(A-Ch,age(Ch,A),L),group_pairs_by_key(L,All).
L = [13-harry, 13-hermione, 13-ron, 14-draco, 30-hagrid, 60-dumbledore],
All = [13-[harry, hermione, ron], 14-[draco], 30-[hagrid], 60-[dumbledore]].
3 Likes

Excelent, I had not thought about using pairs…

Thank you very much peter.ludemann and Capellic.

It’s also instructive to define

age(Age) :- age(_, Age).

and then look at the different results you get for

?- setof(Age, age(Age), Ages).
?- bagof(Age, age(Age), Ages).

and to also look at what happens to the results when you change setof to bagof everywhere.

That last suggestion by peter.ludemann was indeed very instructive…
At first, I didn’t understand the point of the first rule definition. But then, I tested ignoring that rule and embedding the anonymous variable into the setof expression, then I learned something new…
Without the first rule, we get:

?- setof(Age, age(_, Age), Ages).
Ages = [14] ;
Ages = [60] ;
Ages = [30] ;
Ages = [13] ;
Ages = [13] ;
Ages = [13].

But when the first rule is used, we get only one result list…

?- bagof(Age, age(Age), Ages).
Ages = [13, 14, 13, 13, 60, 30].

?- setof(Age, age(Age), Ages).
Ages = [13, 14, 30, 60].

I really appreciate that comment, thank you very much!

An anonymous variable in a setof/3 call doesn’t do what you expect. You need to mark it as “exists”, e.g.:

?- setof(Age, Person^age(Person, Age), Ages).

This can get quite confusing, which is why I prefer to use an auxiliary predicate.

For extra head-scratching, consider the queries

?- age(Age), bagof(Age-People, age_people(Age, People), AgePeople).
?- distinct(age(Age)), bagof(Age-People, age_people(Age, People), AgePeople).
1 Like

Oh, I see…
Seems that the correct goal expression should correspond to:

" ForAll(Age), find the solutions to Exists(Person) such that age(Person,Age), and collect them into AgePeople"

And of course, that is not the same effect as just using an anonymous variable…

Very instructive, I appreciate it and will study harder…
I will test your head-scratching goals and comment on them…
Thanks again…