Шаг 63.
Основы логического программирования.
Создание базы данных, располагающейся в оперативной памяти

    На этом шаге мы рассмотрим создание базы данных, располагающейся в оперативной памяти.

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

    Использование баз данных, располагающихся в оперативной памяти (резидентных), вполне оправданно, если эта БД имеет не слишком большой объем.

    Для начала необходимо задать начальные данные и создать саму базу. Затем наступает черед системы управления базой данных (СУБД), ориентированной на диалог с пользователем. Любая система такого рода должна содержать как минимум такие возможности:

    Эти требования предполагают наличие в системе меню, представляющее пользователю возможность легко ориентироваться при обращениии к стандартными функциям СУБД, а также оконной системы, дающей четкое представление о доступных пользователю средствах.

Обсуждение проекта базы данных

    Лучший способ начать проектирование программы - это нарисовать ее структурную схему (рис. 1).


Рис.1. Структурная схема резидентной БД

    Она показывает, что модуль Меню позволяет пользователю выбрать между четырьмя модулями: process(1) для записи данных в базу, process(2) для удаления данных, process(3) для вывода данных на экран, и process(4) для выхода из системы.

Создание базы данных

    Создадим футбольную БД. Данные представляют собой информацию о пяти футболистах. Они включают имена игроков, названия их команд, номера, позиции, рост, вес, количество сыгранных сезонов. Вся используемая информация приведена в таблице 1.

Таблица 1. Данные об игроках
Имя игрока Команда Номер Позиция Рост Вес Кол-во
Егор Титов Спартак 9 пз 182 72 78
Сергей Овчинников Локомотив 1 в 188 74 82
Дмитрий Булыкин Динамо 11 н 188 80 53
Дмитрий Лоськов Локомотив 9 н 180 78 102
Дмитрий Парфенов Спартак 7 пз 174 70 77

    Для работы с ней необходим предикат, кодирующий эту информацию. Подходящим является:

   player(p_name, /* полное имя игрока (string) */
   t_name,  /*название команды (string) */
   p_number,  /*номер игрока (integer) */
   pos,  /*позиция игрока (string) */
   height,  /*рост (string) */
   weight,  /*вес (integer) */
   nfl_exp)  /*стаж выступлений (integer) */

    Пролог требует, чтобы все утверждения одного и того же предиката были сгруппированы в одном месте. В соответствии с этим требованием группа предиката player записывается в виде:

   player("Егор Титов","Спартак",9,"пз","182",72,78).
   player("Сергей Овчинников","Локомотив",1,"в","188",74,82).
   player("Дмитрий Булыкин","Динамо",11,"н","188",80,53).
   player("Дмитрий Лоськов","Локомотив",9,"н","180",78,102).
   player("Дмитрий Парфенов"," Спартак ",7,"пз","174",70,77).

    Заметим, что в том случае, когда объекты утверждений являются строками и начинаются с заглавных букв, они заключаются в кавычки. Также отметим, что рост игрока задается в виде строки, хотя он и подразумевает числовое значение; в БД как число он не используется.

    Следующей фазой создания БД является задание соответствующих описаний типов. Раздел программы domains будет выглядеть так:

   domains
      p_name, t_name, pos, height=string
      p_number, weight, nfl_exp=integer

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

   database
      dplayer(p_name, t_name, p_number, pos, height, weight, nfl_exp)

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

    В разделе predicates следует описать все другие предикаты, используемые в программе. Эти описания выглядят так:

   predicates
      repeat	/* повтор */
      do_mbase /* цель */
      assert_database /* создание БД */
      menu /* интерфейс в виде меню */
      process(integer) /* различные операции из перечня меню */
      clear_database /* очистка БД */
      player(p_name, t_name, p_number, pos,height, weight, nfl_exp)
      error /* выдача сообщения об ошибке */

    Как уже ранее говорилось, в начале работы программы необходимо занести в динамическую БД предназначенную для нее информацию, содержащуюся в статической БД. Эту задачу выполняет предикат assert_database. Предикат clear_database предназначен для очистки БД перед окончанием работы программы.

    Задачей предиката error является реагирование на ввод неправильной входной информации.

    Предикат player предназначен для задания начального содержимого базы данных, той информации, которая отражена в таблице 1. Когда программа начинает работу, эта информация засылается в утверждения предиката dplayer.

    Предикат do_mbase является главным правилом (модулем) программы. Он также присутствует в целевом утверждении. Предикат menu определяет правило, осуществляющее интерфейс с пользователем при помощи меню. Предикат process(integer) определяет различные правила, выполняющие все возможные операции над БД.

    Раздел программы goal содержит правило do_mbase.

    Теперь можно обобщить все сказанное выше и определить "скелет" нашей программы БД. Тела правил будут определены в разделе программы в разделе программы clauses.

Написание программных модулей

    После завершения стадии проектирования можно приступить к стадии реализации проекта. Теперь нашей задачей явится написание предикатных выражений и вспомогательных правил, которые потребуются основным модулям.

    1. Главный модуль программы do_mbase является одновременно и целью программы:
   do_mbase:-
      assert_database,
      makewindow(1,7,7,"Футбольная база данных",0,0,25,80),
      menu,
      clear_database.

    Модуль засылает в базу информацию из утверждений player, создает окно, выводит меню и очищает БД по окончании работы программы.

    Исходное содержимое БД задается при помощи утверждений с использованием статических предикатов. Правилом для занесения в базу этой информации служит:

   assert_database:- 
      player(P_name,T_name,P_number,Pos,Ht,Wt,Exp),
      assertz(dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp)), 
      fail.
   assert_database:-!.

    Этот предикат использует метод отката после неудачи для перебора всех утверждений player.

    Предикат очистки базы данных:

   clear_database:-
      retract(dplayer(_,_,_,_,_,_,_,_)),
      fail.
   clear_database:-!.

    Так как объекты утверждений dplayer в этом правиле не представляют интереса, то используются анонимные переменные.

    Меню предназначено для удобства пользователя в выборе программных функций. Для обеспечения отвечающего требованиям экранного простанства создается окно на весь экран (25 строк, 80 колонок). Модуль menu выводит четыре доступные пользователю опции:

    Модуль menu в основном состоит из предикатов write, которые выводят на экран перечисленные выше опции.

   menu:-
      repeat,
      clearwindow,
      write(" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"),nl,
      write(" *					  * "),nl,
      write(" * 1. Занесение новой информации об игроках	  * "),nl,
      write(" * 2. Удаление данных об игроках  		  * "),nl,
      write(" * 3. Просмотр информации	  		  * "),nl,
      write(" * 4. Выход из программы			  * "),nl,
      write(" *					  * "),nl,
      write(" * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *"),nl,
      nl,
      write(" Выберите номер: 1, 2, 3 или 4 : "),
      readint(Choice),nl,
      process(Choice),
      Choice=4,
      !.

    Обратим внимание на технику, использованную для обеспечения повторных вызовов модуля menu. Если пользователь введет число, не равное 4 (4 вызывает окончание программы), подцель Choice=4 становится неуспешной, что вызывает откат к предикату repeat.

    2. Модуль для ввода данных

   Правило process(1) предназначено для занесения в базу данных. Этот модуль создает окно для текста, просит пользователя ввести данные с клавиатуры, считывает их и заносит в БД новое утверждение dplayer. Вслед за этим модуль убирает вновь созданное окно и возвращает управление главному меню.

    Отдельное, меньшее по размеру окно создается для обеспечения диалога с программой. За предикатом makewindow, создающим это окно, идут предикаты write, readln и readint, которые информируют пользователя о том, какие данные он должен ввести, и считывают эти данные с клавиатуры:

   process(1):-
      makewindow(2,7,7,"Добавить данные в базу",2,20,18,58), 
      shiftwindow(2), 
      write("Имя игрока: "), 
      readln(P_name), 
      write("Команда: "), 
      readln(T_name),
      . . . .

    Аналогично при помощи write, readln и readint вводятся номер игрока, его позиция, рост, вес, стаж выступлений. За предикатами write, readln и readint следует предикат assertz. Этот предикат помещает новые утверждения dplayer вслед за уже имеющимися.

    Чтобы вводить данные на русском языке можно воспользоваться ,например, руссификатором Паскаля rkega.com, запустив его до загрузки Пролога. Переход на русский и обратно правый и левый Shift.

    Объектами этого нового утверждения являются значения, присвоенные переменным P_name, T_name, P_number и т.п.:

   assertz(dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp)),
   write(P_name," добавлен в базу данных."),
   nl,!,
   write("Нажмите пробел. "),
   readchar(_),
   removewindow.

    Последние строки сигнализируют об окончании процесса ввода и убирают дополнительное окно.

    3. Модуль для удаления данных

    Назначением модуля process(2) является удаление информации из базы данных. Это правило, также как и правило process(1), создает свое собственное окно, запрашивает у пользователя имя игрока и удаляет из БД утверждение, содержащее информацию об этом игроке. После очистки окна управление вновь передается главному меню.

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

   process(2):-
      makewindow(3,7,7,"Удаление игрока из базы данных",10,30,7,40), 
      shiftwindow(3), 
      write("Задайте имя для удаления: "), 
      readln(P_name),
      . . . . .

    Следующая часть правила осуществляет операцию удаления утверждения из БД, посылает короткое сообщение об этом пользователю, ждет нажатия им произвольной клавиши и убирает с экрана дополнительное окно:

   retract(dplayer(P_name,_,_,_,_,_,_,_)),
   write(P_name," был успешно удален из базы данных."),
   nl,!,
   write("Нажмите пробел."),
   readchar(_),
   removewindow.

    Для удаления из базы выбранного пользователем утверждения применен предикат retract. Так как любая другая информация об игроке, кроме его имени, не представляет интерес в данной операции, то на месте всех других объектов стоят анонимные переменные.

    4. Модуль для выборки данных

    Назначением модуля process(3) является поиск содержащихся в базе данных. Этот модуль, как и два уже разобранных, создает свое собственное окно, а затем запрашивает имя игрока. Если в БД находится утверждение, содержащее введенное имя, модуль производит выборку данных и выводит их на экран в удобном формате:

   process(3):-
      makewindow(4,7,7,"Просмотр данных", 7,30,16,47),
      shiftwindow(4),
      write("Укажите имя: "),
      readln(P_name),
      dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp),
      . . . . .

    Предикат dplayer ищет нужное утверждение в базе данных и выбирает строковые и целые значения по каждому запрашиваемому пункту. Целый ряд предикатов write затем выводит полученные значения:

   nl,write(" Имя игрока:	",P_name),
   nl,write(" Команда:	",T_name),
   nl,write(" Позиция:	",Pos),
   nl,write(" Номер:		",P_number),
   nl,write(" Рост:		",Ht," см"),
   nl,write(" Вес:		",Wt," кг "),
   nl,write(" Кол-во игр:	",Exp),
   nl,nl,!,
   nl,write("Нажмите пробел"),
   readchar(_),
   removewindow.

    Если в БД отсутствует утверждение с введенным пользователем именем игрока, программа выдает сообщение об ошибке. Окно для этого сообщения должно располагаться на видном месте, например, в центре экрана. Вариант process(3), отвечающий за выдачу сообщения об ошибке, выглядит так:

   process(3):-
      makewindow(5,7,7," Ошибка ",14,7,5,60),
      shiftwindow(5),
      write("Такого игрока в базе данных нет."),nl,
      nl,!,
      write("Нажмите пробел."),
      readchar(_),
      removewindow,
      shiftwindow(1). 
    5. Модуль окончания работы с программой

    Модуль process(4) обеспечивает нормальное окончание сеанса работы с базой данных. Этот модуль не создает своего собственного окна. Новое окно здесь излишне, так как сообщения очень коротки и не требуют много места на экране. Модуль, однако, требует от пользователя четкого ответа на вопрос, хочет ли он окончить работу с программой:

   process(4):-
      write("Закончить работу с программой? (y/n)"),
      readln(Answer),
      frontchar(Answer,'y',_),!.

    Отметим наличие в правиле предиката frontchar. Он успешен только в том случае, если ответ пользователя на запрос программы начинается с буквы y. Если вводится иная буква, предикат неуспешен, поэтому происходит откат к предикату repeat модуля menu.

    6. Модуль реакции на ошибку

   Аккуратно написанная программа должна надлежащим образом реагировать на допущенные пользователем ошибки при вводе. Если пользователь введет число, меньшее 1 или большее 4, будет успешным одно из правил:

   process(Choice):- 
      Choice<1, error.
   process(Choice):-
      Choice>4, error.
    Оба эти правила вызывают модуль error:
   error:-
      write("Задайте значение от 1 до 4. "),
      write("(Нажмите пробел)"),
      readchar(_). 

Футбольная база данных

    Реализацией приведенного проекта динамической базы данных является программа "Футбольная база данных".

   /* Программа Ф у т б о л ь н а я  б а з а   д а н н ы х	*/
   /* Назначение. Демонстрация примера работающей базы	*/
   /*	            данных. База данных допускает следуюдующие операции: */
   /*                  добавление, удаление и выборку данных. Выборка включает */
   /*	        просмотр данных. */
   domains
      p_name, t_name, pos, height=string
      p_number, weight, nfl_exp=integer
   database
      dplayer(p_name,t_name,p_number,pos,height,weight,nfl_exp) 
   predicates
      repeat
      do_mbase
      assert_database
      menu
      process(integer)
      clear_database
      player(p_name,t_name,p_number,pos,height,weight,nfl_exp)
      error
   goal
      do_mbase.
   clauses
      repeat.
      repeat:-
         repeat.
      /*  Ф у т б о л ь н а я   б а з а   д а н н ы х  */
      player("Егор Титов","Спартак",9,"пз","182",72,78).
      player("Сергей Овчинников","Локомотив",1,"в","188",74,82).
      player("Дмитрий Булыкин","Динамо",11,"н","188",80,53).
      player("Дмитрий Лоськов","Локомотив",9,"н","180",78,102).
      player("Дмитрий Парфенов"," Спартак ",7,"пз","174",70,77).
     /*  К о н е ц  н а ч а л ь н ы х  д а н н ы х  */
      assert_database:-
         player(P_name,T_name,P_number,Pos,Ht,Wt,Exp),
         assertz(dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp)),
         fail.
      assert_database:-!.
      clear_database:-
         retract(dplayer(_,_,_,_,_,_,_)),fail.
      clear_database:-!.
      /* Диалог с этой БД осуществляется по принципу меню. */
      /* При этом используются оконнные средства Пролога. */
      /* Основываясь на запросе пользователя, СУБД активизирует */
      /* соотствующие  процессы для удовлетворения этого запроса */
      /* Меню можно расширить за счет включения */
      /* новых функций. */
      /*  З а д а н и е   ц е л и   в   в и д е   п р а в и л а  */
      do_mbase:-
         assert_database,
         makewindow(1,7,7,"Футбольная база данных",0,0,25,80),
         menu,
         clear_database.
      menu:-
         repeat,
         clearwindow,
         write(" * * * * * * * * * * * * * * * * * * * * * * * * * * * *"),nl,
         write(" *				                            * "),nl,
         write(" * 1. Занесение новой информации об игроках            * "),nl,
         write(" * 2. Удаление данных об игроках  		       * "),nl,
         write(" * 3. Просмотр информации		                     * "),nl,
         write(" * 4. Выход из программы			              * "),nl,
         write(" *					                     * "),nl,
         write(" * * * * * * * * * * * * * * * * * * * * * * * * * * * *"),nl,
         nl,
         write(" Выберите номер: 1, 2, 3 или 4 : "),
         readint(Choice),nl,
         process(Choice),
         Choice = 4,
         !.
         /* Добавление информации об игроке в БД */
      process(1):-
         makewindow(2,7,7,"Добавить данные в базу",2,20,18,58),
         shiftwindow(2),
         write("Имя игрока: "),
         readln(P_name),
         write("Команда: "),
         readln(T_name),
         write("Номер: "),
         readint(P_number),
         write("Амплуа: "),
         readln(Pos),
         write("Рост: "),
         readln(Ht),
         write("Вес: "),
         readint(Wt),
         write("Количество игр: "),
         readint(Exp),
         assertz(dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp)),
         write(P_name," помещен в базу данных."),
         nl,!,
         write("Нажмите пробел. "),
         readchar(_),
         removewindow.
          /* Удаление информации об игроке из БД */
      process(2):-
         makewindow(3,7,7,"Удаление из базы",10,30,7,40),
         shiftwindow(3), 
         write("Задайте имя для удаления: "), 
         readln(P_name), 
         retract(dplayer(P_name,_,_,_,_,_,_)), 
         write(P_name," был успешно удален из базы."), nl, !,
         write("Нажмите пробел."),
         readchar(_),
         removewindow.
        /* Просмотр информации об игроке */
      process(3):-
         makewindow(4,7,7," Просмотр информации",7,30,16,47), 
         shiftwindow(4), 
         write("Задайте имя игрока: "), 
         readln(P_name), 
         dplayer(P_name,T_name,P_number,Pos,Ht,Wt,Exp),nl, 
         nl,write(" Имя:	",P_name),
         nl,write(" Команда:",T_name),
         nl,write(" Амплуа:",Pos),
         nl,write(" Номер:",P_number),
         nl,write(" Рост:",Ht," см"),
         nl,write(" Вес:",Wt," кг "),
         nl,write(" Количество игр:",Exp),
         nl,nl,!,
         nl,write("Нажмите пробел"),
         readchar(_),
         removewindow.
      process(3):-
         makewindow(5,7,7," Ошибка ",14,7,5,60),
         shiftwindow(5),
         write("Такого игрока нет в базе данных."),nl,
         nl,!,
         write("Нажмите пробел."),
         readchar(_),
         removewindow,
         shiftwindow(1).
        /* Выход из диалога */
      process(4):-
         write("Закончить работу с программой? (y/n)"),
         readln(Answer),
         frontchar(Answer,'y',_), !.
       /* Неправильное  обращение к БД */
      process(Choice):-
         Choice<1,error.
      process(Choice):-
         Choice>4,error.
      error:-
         write("Укажите число от 1 до 4."),
         write("(Нажмите пробел для продолжения)"),
         readchar(_).
        /*  К о н е ц  программы  */
    Текст этой программы можно взять здесь.

    Результат работы программы при выборе, например, пункта меню 3- просмотр информации можно посмотреть на рис.2.


Рис.2. Результат работы программы pro63_1.pro

    Каждый из фрагментов программы, спроектированный и описанный в предыдущих разделах главы, занимает отведенное ему в программе место.

    Исходная информация для БД помещается в начале раздела clauses. Когда программа запускается на счет, подцель assert_database создает утверждения dplayer, содержащие такие же данные, что и утверждения статического предиката player, и заносит эти утверждения в динамическую БД. После этого можно добавлять, удалять или просматривать данные, выбирая соответствующие опции меню.

    На следующем шаге мы рассмотрим примеры использования внутренней базы фактов.




Предыдущий шаг Содержание Следующий шаг