PrologEZ
Control & the database · Lesson 29 of 43

Collecting all solutions

findall, bagof, setof, aggregate_all and forall.

So far each answer arrives separately. To work with all answers at once, count them, sum them, sort them, you need the all-solutions predicates.

Here is a small employee database:

% emp(Name, Department, Salary)
emp(ann,   eng,   100).
emp(bob,   eng,    80).
emp(carla, ops,    70).
emp(dave,  ops,    70).
emp(erin,  sales,  90).

findall(Template, Goal, List)

Run Goal to exhaustion; collect a copy of Template for every solution. It never fails, if there are no solutions you get [].

?- findall(Name, emp(Name, _, _), Names)
?- findall(Name-Salary, (emp(Name, _, Salary), Salary >= 80), L)
?- findall(S, emp(_, _, S), Ss), sum_list(Ss, Total), length(Ss, N)
?- findall(X, emp(X, hr, _), L)

setof and bagof

bagof is like findall but fails on no solutions and groups by free variables. setof additionally sorts and removes duplicates. A variable in the goal that doesn't appear in the template is "free" and splits the results into groups, one answer per value:

?- bagof(Name, Salary^emp(Name, Dept, Salary), L)
?- setof(Salary, N^D^emp(N, D, Salary), L)

The Var^Goal syntax says "I don't care about this variable, don't group on it". Without it, bagof/setof would return one answer per distinct value of every free variable. Watch out: even an anonymous _ counts, so bagof(Name, emp(Name, Dept, _), L) would split eng into two groups (salary 100 and salary 80).

aggregate_all/3

For counting and arithmetic summaries there's a shortcut:

?- aggregate_all(count, emp(_, _, _), N)
?- aggregate_all(sum(S), emp(_, ops, S), Total)
?- aggregate_all(max(S), emp(_, _, S), Max)
?- aggregate_all(max(S, Name), emp(Name, _, S), Best)
?- aggregate_all(bag(D), emp(_, D, _), Ds), aggregate_all(set(D), emp(_, D, _), Set)

forall/2: "for every … holds …"

forall(Cond, Action) succeeds if Action holds for every solution of Cond. With side effects it's also the idiomatic loop:

?- forall(emp(_, _, S), S >= 70)
?- forall(emp(_, _, S), S >= 80)
?- forall(emp(N, D, S), format("~w works in ~w and earns ~d~n", [N, D, S]))

Exercise: Department payroll

The emp/3 facts above are loaded. Write total_salary(Dept, Total) giving the sum of salaries in a department. An unknown department should give a total of 0.