Рассмотрим следующее, которое не зависит от встроенных встроенных решений, таких как findall
, setof
или bagof
:
% employees(Single Project, List of Employees
employees(Project, Employees) :-
employees(Project, [], Employees).
employees(Project, Acc, Employees) :-
( designer(Project, Employee, _)
; developer(Project, Employee, _)
),
\+ member(Employee, Acc), !,
employees(Project, [Employee|Acc], Employees).
employees(_Project, Employees, Employees).
Эта версия накапливает уникальный список сотрудников, работающих над проектом. Аналогично, реализация вашего предиката projects_of_all/2
может быть такой:
% projects_of_all(List of Staff, List of Projects)
projects_of_all(Employees, Projects):-
projects_of_all(Employees, [], Projects).
projects_of_all(Employees, Acc, Projects):-
\+ var(Employees),
member(Employee, Employees),
( designer(Project, Employee, _)
; developer(Project, Employee, _)
),
\+ member(Project, Acc), !,
projects_of_all(Employees, [Project|Acc], Projects).
projects_of_all(_Employees, Projects, Projects).
Обратите внимание на защитную подцель \+ var(Employees)
, поскольку мы не хотим, чтобы оба аргумента вызова member(Employee, Employees)
были полностью несвязанными, что может привести к бесконечно рекурсивному расширению переменных в списках с постоянно увеличивающейся длиной. Как только мы выбираем Employee
, любой связанный Project
извлекается через designer/3
или developer/3
(оставляя точки выбора), пока не будет найден новый Project
, еще не накопленный, и тогда мы искать больше; пока их больше нет, в этом случае мы останавливаемся (2-й пункт - базовый вариант).
Хотя это, вероятно, неэффективно по отношению к любой внутренней (то есть нативной, неинтерпретированной) реализации findall
, setof
или bagof
, он служит для демонстрации подхода, призванного помочь вам понять решение используя аккумуляторные методы.
Если вам требуется использование встроенных комплексных решений, вы можете реализовать projects_of_all/2
следующим образом:
% projects_of_all(List of Staff, List of Projects)
projects_of_all(Employees, Projects):-
findall(Project,
( member(Employee, Employees),
( designer(Project, Employee, _)
; developer(Project, Employee, _)
)
), ProjectsBag),
sort(ProjectsBag, Projects).
Обратите внимание, что setof
и bagof
будут возвращаться, чтобы дать вам альтернативы, но вы хотите, чтобы все проекты в списке были собраны, что является поведением findall
. Предположительно, однако, вам не нужны дубликаты, поэтому вызов sort/2
для результата, как показано, удаляет дубликаты, чтобы получить набор.
РЕДАКТИРОВАТЬ: ОП изменил (уточнил) вопрос после того, как я написал это, что потребовало совершенно другого ответа (объяснение ниже):
% projects_of_all(List of Staff, List of Projects)
projects_of_all(Employees, CommonProjects):-
% find the projects of every employee in the input list
employee_projects(Employees, EmployeeProjects),
% find the intersection of all projects (common projects)
recursive_val_intersect(EmployeeProjects, CommonProjects).
employee_projects([], []).
employee_projects([Employee|Employees], [Projects|Rem]) :-
findall(Project,
( designer(Project, Employee, _)
; developer(Project, Employee, _)
),
ProjectsBag),
sort(ProjectsBag, Projects),
employee_projects(Employees, Rem).
recursive_val_intersect([L|Ls], Intersect) :-
recursive_val_intersect(Ls, L, Intersect).
recursive_val_intersect([], Acc, Acc).
recursive_val_intersect([L0|Ls], L1, Intersect) :-
intersection(L0, L1, NewL),
recursive_val_intersect(Ls, NewL, Intersect).
employee_projects/2
используется для создания списка списков проектов, над которыми работал каждый Employee
во входном списке. Обратите внимание, что он использует стратегию решения findall/3
, которую я использовал ранее. Второй предикат, recursive_val_intersect/2,3
, определяет пересечение всех списков проектов, поскольку это указывает на проекты, над которыми каждый сотрудник работал вместе . Это отличается от приведенного выше решения, которое просто ищет все проекты, над которыми работали все сотрудники, в списке ввода, к чему я и стремился.
Обратите внимание, что recursive_val_intersect/3
выше полагается на предикат пересечения множеств SWI-PROLOG intersection/3
, который принимает списки без дубликатов (следовательно, использование sort/2
для построения входных списков в employee_projects/2
).