Ответ в теме: Предприятие (база данных на Prolog)

      Комментарии к записи Ответ в теме: Предприятие (база данных на Prolog) отключены
#3197

В заданиях речь идет про работников предприятия и вакансии, поэтому соответствующие элементы должны быть в базе данных. Если мы пишем на Tubro Prolog или Visual Prolog – то описываем типы данных, о которых идет речь в разделе domains и структуру БД в разделе database. Для SWI Prolog это не требуется:

DOMAINS
	subdivision = string
	staff = string
	pay = integer
	age = integer
	education = middle; bachelor; engineer; magister; phd
	name = string
	sex = male;female
	
	subdivisions = subdivision*
	ages = age*
	staffs = staff*
DATABASE
	vacancy(subdivision, staff, pay, age, age, education)
	staff(name, subdivision, staff, pay, age, education, sex)

Теперь в разделе clauses можно описать базу данных:

	vacancy("technical", "programmer", 12000, 19, 24, engineer).
	vacancy("technical", "administrator", 18000, 23, 40, bachelor).
	vacancy("management", "human resources manager", 11000, 18, 30, middle).
	
	staff("petr", "technical", "programmer", 14000, 25, magister, male).
	staff("ivan", "technical", "programmer", 12000, 22, engineer, male).
	staff("ula", "management", "human resources manager", 18000, 35, bachelor, female).

Первое задание заключается в том, что нужно вывести информацию о вакансиях, мы уже можем сделать это следующим запросом:

goal
	 vacancy(Sub, Staff, Pay, AgeFrom, AgeTo, Education),
	 write(Sub), write(" "), write(Staff), write(" "), 
	 write(Pay), write(" "), write(AgeFrom), write(" "), 
	 write(AgeTo), write(" "), write(Education), nl.

Если мы пишем на Visual Prolog или SWI Prolog, то достаточно оставить только первую строку в запросе – значения всех переменных будут выведены автоматически, однако в Turbo Prolog нужно делать это явно с помощью функции write.

Схема запроса:
request-diagram-prolog-vacancies

Во втором задании требуется вывести работников, подходящих под вакансии. Для этого нам опять нужно перебрать в базе данных вакансии, а затем перебрать работников (что-то типа вложенного цикла) и проверить их характеристики:

goal
	vacancy(VSubdivision, VStaff, VPay, VAgeFrom, VAgeTo, VEducation),
	staff(SName, _, SStaff, _, SAge, SEducation, _),
	SAge <= VAgeTo, SAge >= VAgeFrom, 
	education_rank(VEducation, VEducationRank),
	education_rank(SEducation, SEducationRank),
	VEducationRank <= SEducationRank. 

В данном случае, я проверяю соответствие по возрасту и образованию. Видно, что я добавил предикат, который присваивает каждому типу образования ранг (некоторый номер, с помощью которого мы можем сравнивать образования):

	education_rank(middle, 0).
	education_rank(bachelor, 1).
	education_rank(engineer, 2).
	education_rank(magister, 3).
	education_rank(phd, 4).

Получается следующая схема для этого запроса:
request-diagram-prolog-staff

В третьей части задания нужно получить средний возраст сотрудников в каждом отделе, причем раздельно для мужчин и женщин. Решить такую задачу сразу достаточно тяжело – разделим ее. Получим сначала список отделов без повторений, а затем для каждого из них получим данные по среднему возрасту. Цель будет выглядеть так:

goal
  get_subs(Subs), average_age(Subs).

Предикат get_subs вернет список отделов без повторов. Для этого нужно получить с помощью findall список предприятий из всех записей базы данных о работниках, а затем удалить из них повторы с помощью функции list-to-set:

get_subs(UniqueSubs):-
  findall(Sub, staff(Name, Sub, Staff, Pay, Age, Edu, Sex), Subs),
  list_to_set(Subs, UniqueSubs).

Функция average_age принимает список отделов и для каждого элемента считает средний возраст, который выводит на экран. Для этого она считает суммы возрастов мужчин и женщин, а также их количества, а затем вызывает функцию print_average для вычисления и вывода на экран результата:

average_age([]):-!.
average_age([Head|Tail]):-
	sum_age_and_count(Head, male, MaleSumAge, MaleCount),
	sum_age_and_count(Head, female, FemaleSumAge, FemaleCount),
	print_average(Head, male, MaleSumAge, MaleCount),
	print_average(Head, female, FemaleSumAge, FemaleCount),
	average_age(Tail).

Отдельная функция для вывода нужна потому, что если в отделе нет женщин или мужчин – то мы получим деление на ноль при вычислении среднего значения. Такой случай нужно обработать отдельно:

print_average(Sub, Sex, _Age, 0):-
	!, write("no "), write(Sex), write(" in "), write(Sub), nl.
print_average(Sub, Sex, Age, Count):-
	Average = Age/Count,
	write("average age of "), write(Sex), write(" in "), write(Sub), 
	write(": "), write(Average), nl.

Наконец, для вычисления суммы возрастов для конкретного отдела и пола мы можем с помощью findall получить список возрастов, а затем вычислить его суммы с помощью sum_list. Для получения количества лиц в этом случае достаточно использовать функцию length:

sum_age_and_count(Sub, Sex, Sum, Count):-
	findall(Age, staff(Name, Sub, Staff, Pay, Age, Education, Sex), Ages),
	sum_list(Ages, Sum),
	length(Ages, Count).

Чтобы найти самую распространенную должность мы напишем функцию:

goal
	most_popular_staff(MostPopularStaff).

Мы будем считать количество сотрудников на каждой должности, поэтому сначала получим список всех должностей без повторов с помощью findall и list_to_set, как уже делали выше. Мы будем определять наибольшее число вхождений, но нам для этого нужно инициализировать чем-то начальное значение максимума – мы возьмем первую попавшуюся должность из списка и определим для нее число сотрудников (функцией count):

most_popular_staff(MostPopularStaff):-
	findall(Staff, staff(Name, Sub, Staff, Pay, Age, Edu, Sex), AllStaffs),
	list_to_set(AllStaffs, UniqueStaffs), 
	UniqueStaffs = [HeadStaff|_], 
	count(AllStaffs, HeadStaff, Count),
	most_popular_staff(UniqueStaffs, AllStaffs, HeadStaff, Count, MostPopularStaff).

В данном случае, функция не является рекурсивной. Вызываемая в конце функция имеет другое количество параметров. Она принимает список должностей без повторов, список должностей с повторами, текущие параметры самой популярной должности (название и количество) и возвращает самую популярную должность. Текущие параметры выступаю в качестве буфера, в котором накапливается результат (см. Метод накапливающего параметра):

most_popular_staff([], _AllStaffs, Staff, _Count, Staff):-!.
most_popular_staff([Head|Tail], AllStaffs, Staff, Count, MostPopularStaff):-
	count(AllStaffs, Head, HeadCount),
	HeadCount > Count, !,
	most_popular_staff(Tail, AllStaffs, Head, HeadCount, MostPopularStaff);
	most_popular_staff(Tail, AllStaffs, Staff, Count, MostPopularStaff).