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

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

В этой теме 1 ответ, 2 участника, последнее обновление  Васильев Владимир Сергеевич 7 мес., 2 нед. назад.

  • Автор
    Сообщения
  • #3196

    questioner
    Участник

    Нужно написать программу, реализующую работу с базой данных «предприятие» и начертить схемки для запросов:

    1. Какие вакансии имеются на данном предприятии?
    2. Кто из работающих сотрудников может претендовать на вакантную должность?
    3. Каков средний возраст сотрудников (мужчин и женщин отдельно) в различных подразделениях предприятия?
    4. Какая профессия наиболее распространена на предприятии?
  • #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).

Для ответа в этой теме необходимо авторизоваться.