Разработка экспертных систем средствами языка Турбо-Пролог

Тип работы:
Учебное пособие
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Министерство образования Российской Федерации

Воронежская государственная технологическая академия

Кафедра математического моделирования

информационных и технологических систем

РАЗРАБОТКА ЭКСПЕРТНЫХ СИСТЕМ СРЕДСТВАМИ ЯЗЫКА ТУРБО-ПРОЛОГ

Методические указания к практическим занятиям

по курсу «Представление знаний в информационных системах»

Для студентов специальности

71 900 — «Информационные системы и технологии», обучающихся по направлению 654 700 — «Информационные системы»,

дневной формы обучения

Воронеж 2003

УДК 51(075); 681.3. 06

Разработка экспертных систем средствами языка Турбо-Пролог: Методические указания к практическим занятиям по курсу «Представление знаний в информационных системах» / Воронеж. гос. технол. акад.; Сост. И. О. Павлов, С. В. Кулакова. Воронеж, 2003. 36 с.

Задания разработаны в соответствии с требованиями ООП подготовки инженеров по направлению 654 700 — «Информационные системы», специальности 71 900 — «Информационные системы и технологии». Они предназначены для закрепления теоретических знаний дисциплины цикла ОПД.

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

Библиогр.: 7 назв.

Составители: доцент И.О. ПАВЛОВ,

доцент С.В. Кулакова

Научный редактор профессор Г. В. АБРАМОВ

Рецензент профессор И.Г. АМРАХОВ

Печатается по решению

редакционно-издательского совета

Воронежской государственной технологической академии

г Павлов И. О. ,

Кулакова С.В., 2003

У Воронежская государственная Технологическая академия, 2003

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

РАБОТА С БАЗАМИ ДАННЫХ В ОПЕРАТИВНОЙ ПАМЯТИ

Сама форма записи фактов в Прологе наводит на мысль о работе с базой данных (БД). Например, набор фактов

student («99−231»,"Иванов С.С. «,"АТП»).

student («99−232»,"Петров А.Г. «,"Технологический»).

student («99−233»,"Степанов И.А. «,"АТП»).

student («99−234»,"Иванов А.В. «,"ПМА»).

student («99−235»,"Демидов С.А. «,"АТП»).

задает таблицу вида

Шифр студента

(номер зачетной книжки)

Фамилия студента

Факультет

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

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

exam («99−231″,"Информатика», 4).

exam («99−232″,"Информатика», 5).

exam («99−231″,"Математика», 5).

exam («99−233″,"Математика», 3).

exam («99−235″,» Информатика «, 5).

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

goal

exam (Num, ,"Информатика", 5), student (Num, Name, «АТП»), write (Name), nl, fail.

Пролог-программа просматривает все факты с предикатом exam до тех пор, пока не находит факт exam («99−235″,"Информатика», 5), соответствующий второму предикату правила. В результате чего свободная переменная Num получает значение «99−235». Таким образом, первый предикат цели согласован, и программа переходит к согласованию следующего предиката student («99−235», Name, «АТП»). Так как в программе имеется факт student («99−235»,"Демидов С.А. «,"АТП»), предикат оказывается истинен, и в результате переменная Name получает значение «Демидов С. А. «, которое и будет выведено на экран в результате проверки предиката write (Name). Оставшиеся предикаты nl и fail предназначены для просмотра и поиска информации по всему списку студентов.

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

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

database [- < имя БД> ]

dbpred1(…)

dbpred2(…)

Например,

database — tabel

dstudent (symbol, symbol, symbol.)

dexam (symbol, symbol, integer)

Перечисленные здесь предикаты после подстановки в них вместо переменных констант (т. е. превращения их в факты) могут быть помещены и, если потребуется, удалены во время выполнения программы в динамическую базу данных. Делается это с помощью стандартных (встроенных в систему) предикатов: asserta, assertz, retract, save, consult, readterm, findall. В программе можно использовать несколько разделов database, при этом каждому из них нужно назначить уникальное имя. Если имя разделу database не назначено (такой раздел в программе может быть только один), компилятор по умолчанию назначает имя dbasedom.

Стандартные предикаты для работы с динамическими БД

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

asserta (< факт>)

Таким образом, чтобы поместить в БД утверждение

dstudent («99−233»,"Степанов И.А. «,"АТП»)

перед уже имеющимся там утверждением

dstudent («99−234»,"Иванов А.В. «,"ПМА»),

стоящим в настоящий момент в базе данных на первом месте, необходимо следующее предикатное выражение

asserta (dstudent («99−233»,"Степанов И.А. «,"АТП»)).

2. Предикат assertz так же, как и asserta, заносит новые утверждения в базу данных. Однако он помещает новое утверждение за всеми уже имеющимися в базе утверждениями того же предиката. Синтаксис предиката тот же. Например,

assertz (dstudent («99−235»,"Демидов С.А. «,"АТП»)).

Замечание: иногда бывает предпочтительно иметь часть информации базы данных в виде утверждений статической БД; эти данные заносятся в динамическую БД сразу после активизации программы. Для этой цели используются предикаты asserta и assertz. В общем, предикаты статической БД имеют другое имя, но ту же самую форму представления данных, что и предикаты динамической. Например,

predicates

student (symbol, symbol, symbol)

database

dstudent (code, name, dep)

clauses

student («99−231»,"Иванов С.С. «,"АТП»).

student («99−232»,"Петров А.Г. «,"Технологический»).

student («99−233»,"Степанов И.А. «,"АТП»).

student («99−234»,"Иванов А.В. «,"ПМА»).

student («99−235»,"Демидов С.А. «,"АТП»).

Все отличие предиката dstudent по сравнению с student заключается лишь в одной лишней букве терма. Добавление латинской буквы d — обычный способ различать предикаты динамической и статической баз данных.

А правилом для занесения в динамическую БД информации из утверждений предиката student служит

assert_database : — student (Code, Name, Dep),

assertz (dstudent (Code, Name, Dep)), fail.

assert_database : — !.

3. Предикат retract удаляет утверждение из динамической БД (еще раз напомним, что динамическая БД содержит факты, но не правила) Его синтаксис таков:

retract (< факт>).

Например правило

del: — retract (exam (99−231,inf, 5)).

удаляет из БД факт exam (99−231,inf, 5), а правило

del_all: -retract (_,_,_), fail.

удаляет из нее все факты.

4. Предикат findall позволяет собрать все имеющиеся в базе данные в список, который может быть полезен при дальнейшей работе. Синтаксис:

findall (< элемент_списка>,<факт>,<список>)

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

findall (Name, dstudent (_, Name,_), Student_list)

переменная Student_list будет содержать список имен всех студентов, содержащихся в фактах с термом dstudent.

Предикаты для работы с БД в целом

Для записи динамической БД в файл на диск и для загрузки содержимого файла в динамическую БД применяются стандартные предикаты save и consult.

Предикат save сохраняет находящуюся в оперативной памяти базу данных в текстовом файле. Синтаксис этого предиката

save (имя_файла[, имя_БД]),

где имя_файла есть произвольное допустимое в MS DOS или PC DOS имя файла.

Пример: для того, чтобы сохранить содержимое БД tabel в файле с именем exam03. dba, требуется предикат

save («exam03. dba», tabel).

В результате все утверждения находящейся в оперативной памяти динамической БД tabel будут записаны в файл exam03. dba. Cодержимое БД со стандартным именем dbasedom сохранить в файле c: dir1f1. db можно при помощи предиката

save («c: \dir1\f1. db»).

Заметим, что если файл с таким именем уже существовал на диске, то этот старый файл будет затерт. Поэтому следует использовать предикат save с известной долей осторожности.

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

consult (< имя_файла>[,<имя_БД>]).

Для загрузки файла exam02. dba в БД tabel требуется выражение

consult («exam02. dba «, tabel).

А для того, чтобы загрузить содержимое файла d: dir2f2. dbf в БД со стандартным именем dbasedom необходимо использовать предикат

consult («d: \dir2\f2. dbf»).

Предикат consult неуспешен, если файл с указанным именем отсутствует на диске, или если этот файл содержит ошибки, как, например, в случае несоответствия синтаксиса предиката из файла описаниям из раздела программы database, или если содержимое файла невозможно разместить в памяти ввиду отсутствия места.

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

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

Хотелось бы реализовать достаточно логичным удобным для пользователя способом. Такие требования предполагают наличие в системе меню.

Приведем пример простейшей программы, позволяющей выполнять основные действия над динамической БД.

database

dstudent (symbol, symbol, symbol) /*Таблица с информацией о студентах */

dexam (symbol, symbol, integer) /* Таблица с информацией о сдаче экзаменов*/

predicates

menu

reaction (char)

deldb

typerec (char)

addquation3(char)

retquation4(char)

findquation4(char)

find (integer)

findname (symbol)

findnameyes (symbol)

findexam (symbol, symbol, integer)

find4(symbol, integer)

dely

goal

makewindow (1, 10, 3, «БД `сессия' «, 3, 10, 19, 60, 1, -1,

«218 191 192 217 196 192»), menu.

clauses

/*Вывод меню на экран*/

menu: — clearwindow,

write («1 — создать»), nl,

write («2 — загрузить»), nl,

write («3 — добавить запись»), nl,

write («4 — найти запись»), nl,

write («5 — удалить запись»), nl,

write («6 — сохранить»), nl,

write («7 — выход»), nl, readchar (N), reaction (N).

/*Реакция программы на выбор пункта меню*/

reaction ('1'): -deldb, menu.

reaction ('2'): -clearwindow, write («Введите имя файла»), readln (Fname),

consult (Fname), menu.

reaction ('3'): -typerec (N), addquation3(N), dely, menu.

reaction ('4'): -clearwindow, typerec (N), findquation4(N), menu.

reaction ('5'): -clearwindow, typerec (N), retquation4(N), dely, menu.

reaction ('6'): -write («Enter filename»), readln (Fname), save (Fname), menu.

reaction ('7').

reaction (_): -write («Введите число 1−6»), dely, menu.

/*Удаление всей информации из базы*/

deldb: -retract (dstudent (_,_,_)), fail.

deldb: -retract (dexam (_,_,_)), fail.

deldb.

/*Выбор типа записи (таблицы БД)*/

typerec (N): -clearwindow, write («1 — информация о студентах»), nl,

write («2 — результаты сдачи экзаменов»), nl, readchar (N).

/*Ввод и добавление новой записи*/

addquation3('1'): -clearwindow, write («Введите шифр студента»), nl, readln (Code),

write («Введите ФИО студента»), nl, readln (Name),

write («Введите факультет»), nl, readln (D),

assertz (dstudent (Code, Name, D)), write («Запись добавлена»), nl.

addquation3('2'): -clearwindow, write («Введите шифр студента «), nl, readln (Code),

write («Enter course»), nl, readln (Course),

write («Enter mark»), nl, readint (Mark),

assertz (dexam (Code, Course, Mark)), write («Запись добавлена «).

addquation3(_): — write («Ошибка!»).

/*Уточнение задания на поиск информации*/

findquation4('1'): -clearwindow, write («1 — искать запись по шифру»), nl,

write («2 — искать запись по имени»), nl, readint (N), find (N), dely.

findquation4('2'): -clearwindow, write («1 — искать запись по шифру «), nl,

write («2 — искать запись по наименованию дисциплины «), nl,

readint (N), N2=N+2, find (N2), dely.

/*Уточнение задания на поиск информации */

find (1): -clearwindow, write («Введите шифр «), readln (Code),

dstudent (Code, Name, D), write («Студент «), write (Name), nl,

write («обучается на факультете «), write (D).

find (1): -write («Такой записи в базе нет»).

find (2): -clearwindow, write («Введите имя студента «), readln (Name),

findname (Name), findnameyes (Name).

find (2): -write («Такой записи в базе нет «).

find (3): -clearwindow, write («Введите шифр студента «), readln (Code),

dstudent (Code, Name, D), write («Студент «), write (Name), nl,

write («факультета»), write (D), nl, findexam (Code,_,_).

find (3): -write («Такой записи в базе нет «).

find (4): -clearwindow, write («Введите наименование дисциплины»),

readln (Course),

write («Введите оценку. Для поиска по всем оценкам введите 0 «),

readint (Mark), find4(Course, Mark).

/*Поиск информации о сдаче экзаменов */

find4(Course, 0): -findexam (_, Course,_).

find4(Course, Mark): -findexam (_, Course, Mark).

/*Поиск записей по имени студента*/

findname (Name): -dstudent (Code, Name, D), write («Шифр студента «),

write (Code), nl,

write («Факультет «), write (D), dely, fail.

findname (_).

/*Результат поиска записей по имени*/

findnameyes (Name): -dstudent (_, Name,_).

/*Поиск информации о сдаче экзаменов*/

findexam (Code, Course, Mark): -dexam (Code, Course, Mark),

write («Шифр»), write (Code), write (««),

write (Course), write (««), write (Mark), nl, fail.

findexam (_,_,_).

/*Определение и удаление строки из БД*/

retquation4('1'): -write («Введите шифр студента»), nl, readln (Code),

write («Введите имя»), nl, readln (Name),

write («Введите название факультета»), nl, readln (D),

retract (dstudent (Code, Name, D)), write («Запись удалена»), nl.

retquation4('2'): -write («Введите шифр студента «), nl, readln (Code),

write («Введите название дисциплины»), nl, readln (Course),

retract (dexam (Code, Course,_)),

write («Запись удалена»), nl.

retquation4(_): -write («Такой записи в базе нет»), nl.

/*Приостановка работы программы до нажатия любой клавиши*/

dely: -nl, nl, nl, write («Нажмите любую клавишу»), nl, readchar (_).

/* Конец программы */

РАБОТА С БД НА ДИСКЕ

Общие правила работы с файлами на внешних носителях

В системе Турбо-Пролог имеются предикаты для работы с файлами в целом:

deletefile (< имя_файла>) — уничтожает файл < имя_файла>;

save (< имя_файла>) — сохраняет на диске утверждения динамической базы данных;

renamefile (< старое_имя_файла>,<новое_имя_файла>) — переименование файла;

existfile (< имя_файла>) — тест на наличие файла с данным именем. Предикат успешен в случае, если файл с именем, заданным аргументом < имя_файла>, присутствует в директории;

flush (< файловая_переменная>) — предикат сбрасывает содержимое внутреннего буфера, отведенного для данного устройства записи.

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

dir (< путь>,<шаблон>,<имя_файла>) — выдача списка файлов каталога. Переменной < путь> должен быть присвоен корректный путь доступа, текстовая переменная < шаблон> задает шаблон группы файлов, представляющей интерес. Данный предикат выдает каталог имен файлов подходящих под заданный шаблон; вы можете выбрать среди них нужный и нажать Enter. Имя файла будет присвоено переменной < имя_файла>.

Так, в приведенном выше примере программы работы с БД в оперативной памяти правило reaction (6) сохранения БД в файле на диске можно заменить (добавив соответствующие описания) следующей группой правил.

reaction ('6'): -savedb, nl, menu.

savedb: — write («Введите имя файла»), readln (Fname), rwrite (Fname).

rwrite (««): — disk (Dir), dir (Dir,"*. dba», Fname1), save (Fname1).

rwrite (Fname): — existfile (Fname), write («Файл с таким именем уже существует. Перезаписать (y/n)?»), nl, readchar (Yes), rwr (Yes, Fname).

rwrite (Fname): — not (existfile (Fname)), save (Fname).

rwr ('y', Fname): -save (Fname).

rwr ('n',_): — write («Enter new filename»), readln (Fname1),

rwrite (Fname1).

rwr ('Y', Fname): -rwr ('y', Fname).

rwr ('N', Fname): -rwr ('n', Fname).

Здесь правило rwrite предназначено для определения имени файла, и сохранения в нем данных из базы. При этом если введено имя уже существующего файла, будет выдан запрос о перезаписи с уничтожением прежней информации (предикат rwr), а если имя файла введено не было, программа предоставит возможность выбора имени из списка (предикат dir).

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

reaction ('1'): — write («Сохранить изменения (y/n)?»),

readchar (Yes), nl, savequation1(Yes), nl, deldb,

menu.

savequation1('n').

savequation1('N').

savequation1('y'): -savequation1('Y').

savequation1('Y'): -savedb.

savequation1(_): -write («Нажмите, пожалуйста, y или n»),

readchar (Yes), nl, savequation1(Yes).

Здесь предикат savequation1 задает реакцию на ответ пользователя на запрос о сохранении имеющихся данных. Очевидно, что такой запрос можно включить и в правило reaction (2) перед загрузкой новой БД и в правило reaction (7) перед окончанием работы программы.

reaction ('2'): — disk (Dir), dir (Dir,"*. dba", Fname),

clearwindow, write («Сохранить изменения (y/n)?»),

readchar (Yes), nl, savequation1(Yes), nl,

consult (Fname), menu.

reaction ('7'): — write («Сохранить изменения (y/n)?»),

readchar (Yes), nl, savequation1(Yes), nl.

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

Описание файлового домена

Описание одного файлового домена с именем datafile выглядит как

file = datafile

Это описание отличается от привычного описания типа

name = symbol,

где name является именем домена, а symbol — стандартным типом доменов Турбо-Пролога.

1. При описании файловых доменов, стандартный тип домена file задается по левую сторону от знака равенства, а имя домена datafile — по правую.

2. datafile, хоть и описывается как домен, но используется в программе (при записи правил) как символическое имя файла, то есть идентификатор, который в программе может быть логически отождествлен с любым DOS файлом. Это символическое имя называют также логическим именем файла.

В описании file можно указывать несколько символических имен, но само описание должно быть единственным. Если в программе вводится несколько символических имен файлов, то они разделяются между собой точкой с запятой (;). Например,

file = datafile1; datafile2; datafile3

Это объявление декларирует три символических имени для последующего их отождествления с реальными DOS файлами.

Операции чтения-записи в файл

Запись в файл

Перед тем, как начать писать что-либо в файл, нужно создать его. В Турбо-Прологе файлы создаются при помощи предиката openwrite. При этом, указатель файла помещается в его начало. Синтаксис:

openwrite (< символическое имя файла>, < имя_DOS_файла>).

Например

openwrite (datafile1,"file1. dat"),

где datafile1 есть введенный пользователем файловый домен, а file1. dat — физическое имя файла на диске. Предикат устанавливает связь между объектами datafile1 и file1. dat. Теперь ссылки на datafile1 будут означать обращение к file1. dat. Связь остается в силе вплоть до закрытия файла.

Заметим, что если файл с именем file1. dat к моменту вызова предиката openwrite уже присутствовал в директории, то его содержимое будет утрачено. Для того, чтобы застраховаться от этой неприятности, можно сначала проверить наличие файла при помощи предиката

existfile («file1. dat»)

и принять соответствующие меры, если existfile будет успешен.

Чтобы использовать стандартные предикаты write или writef для вывода информации в файл, необходимо назначить этот файл в качестве устройства записи:

writedevice (< символическое имя файла>).

Например

writedevice (datafile1), write («Эти три строки будут записаны в файл file1. dat»), nl, write («Это вторая строка»), nl, write («А это — третья «).

Дозапись в конец уже существующего файла

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

openappend (< символическое имя файла>, < имя_DOS_файла>).

Например,

openappend (datafile2,"file2. dat")

Дальнейшая работа (переадресация вывода, и вывод данных) аналогична работе с новым файлом.

Чтение из файла

Для чтения данных из файла требуется:

1. Открыть файл при помощи предиката openread:

openread (< символическое имя файла>, < имя_DOS_файла>).

Пример:

openread (datafile3,"file3. dat").

2. Назначить файл datafile3 устройством чтения:

readdevice (< символическое имя файла>).

Например,

readdevice (datafile1).

3. Ввести информацию из файла при помощи соответствующего предиката или правила (readint, readreal, readln и т. д.).

Модификация существующего файла

Турбо-Пролог позволяет открывать файлы для модификации, то есть для чтения и записи одновременно. Для этой цели служит предикат openmodify.

openmodify (< символическое имя файла>, < имя_DOS_файла>).

Пример

openmodify (datafile1,"file4. dat")

Предикат успешен только в том случае, если файл уже присутствует на диске.

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

Указатель файла может быть помещен в любую позицию. Файлы с такой организацией доступа к хранящейся в ней информации называются файлами прямого доступа, а операции выборки из такого файла — вводом-выводом из файла прямого доступа.

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

filepos (< символическое_имя_файла>, < позиция_указателя>, < тип_смещения>).

Параметру < позиция_указателя> здесь должно быть присвоено действительное число, обозначающее позицию в файле, из которой будет считан или в которую будет записан символ (дробная часть числа отбрасывается).

Параметру < тип_смещения> может быть присвоено одно из трех значений: 0, 1 или 2. Эти значения определяют то, как будет интерпретировано значение < позиция_указателя>. В табл. 1 пояснено употребление каждого допустимого значения параметра < тип_смещения>.

база данные файл диск

Таблица 1

Режимы работы предиката filepos

Тип смещения

Действия системы

0

Смещение берется относительно начала файла

1

Смещение берется относительно текущей позиции

2

Смещение берется относительно конца файла

Рассмотрим, для примера, такое употребление предиката filepos:

filepos (datafile1,100,0).

В этом выражении datafile1 есть имя логического файла. Параметр < позиция_указателя> имеет значение 100, что указывает на то, что будет прочитан символ, стоящий в 100-й позиции (101-й символ). Значение 0 параметра < тип_смещения> свидетельствует о том, что сотая позиция будет отсчитываться от начала файла.

Если в этой записи 0 заменить на 1,

filepos (datafile1,100,1),

то отсчет будет вестись уже относительно текущей позиции указателя. Если данный предикат будет успешен, то указатель сдвинется еще на 100 позиций вперед.

Закрытие файла

Этой цели служит предикат

closefile (< символическое имя файла>).

Например,

closefile (datafile1).

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

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

Переадресация ввода-вывода

Встроенный предикат Турбо-Пролога readdevice позволяет переадресовать логическое устройство ввода. Аналогично встроенный предикат writedevice осуществляет переадресацию устройства вывода информации.

Так предикат

readdevice (myfile)

настраивает устройство ввода на файл myfile, а предикат

writedevice (yourfile)

настраивает устройство вывода на файл yourfile.

Для настройки ввода и вывода на стандартные устройства в Турбо-Прологе используются стандартные имена screen, keyboard, printer. Например, правило

write_lines : — writedevice (screen), write («Первая строка «),

nl, writedevice (printer),

write («Первая строка»), nl,

flush (printer), writedevice (screen).

выводит строку «Первая строка «на экран монитора, а затем эту же строку выводит на принтер. Стандартный предикат flush здесь используется для того, чтобы строка была выведена из буфера сразу на принтер.

Другие полезные предикаты

Предикат eof производит проверку на достижение конца файла

eof (< символическое_имя_файла>)

Для автоматизации процесса открытия различных файлов бывает полезно использования операций конкатенации строковых переменных, для чего в Турбо-Прологе используют стандартный предикат concat:

concat (< строка1>,<строка2>, < новая_строка>).

Особенности работы с БД, хранящими данные в файлах на диске

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

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

Предикаты нерезидентной БД объявляются в разделе программы database так же, как и предикаты резидентной.

database

Stud (symbol, symbol, symbol)

database — test

Exam (symbol, symbol, integer)

predicates

Add_stud (dbasedom)

Add_exam (test)

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

Add_stud (Term): -Write («Ведите шифр»), readln (Code),

Write («Ведите имя»), readln (Name),

Write («Ведите факультет «),

Rreadln (Dep),

Term=stud (Code, Name, Dep),

Write (Term), Asserta (Term).

Add_exam (Term): -Write («Ведите Шифр»), readln (Code),

Write («Ведите название дисциплины»),

Readln (Course),

Write («Ведите оценку»), readln (Mark),

Term=exam (Code, Course, Mark),

Write (Term), Asserta (Term).

Здесь правило Add_stud позволяет ввести запись (переменная Term) и добавить ее в резидентную БД dbasedom. А правило Add_exam вводит запись и добавляет одну запись в базу test.

Корме того, для чтения из файла объектов, относящихся к определенному в программе домену, используется специальный предикат readterm. Синтаксис этого предиката

readterm (< имя домена > ,< значение объектов домена>).

Рассмотрим, к примеру, предикатное выражение

readterm (test, Term)

Здесь, предикат успешен, если с установленного устройства ввода была прочитана строка, совпадающая по синтаксису с записью БД test. Например, exam («99−291″,"Информатика», 3). Тогда переменная Term получает значение этой записи. То есть, предикат Term=exam («99−291″,"inf», 3) истинен.

Рассмотрим другой пример

readterm (test, exam (Code, Course, Mark))

Предикат успешен также, если с установленного устройства ввода была прочитана строка, совпадающая по синтаксису с записью БД test, но этот предикат уже определяет значения полей записи, то есть переменная Code получает значение «99−291», переменная Course — значение «Информатика», а переменная Mark -3.

РАЗРАБОТКА ЭКПЕРТНЫХ СИСТЕМ

Разработка экспертных систем (ЭС) является наиболее плодотворной быстро развивающейся областью применения Пролога. Очень важно понимать, как работают экспертные системы, так как они могут использоваться, фактически, в любой области знаний.

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

Структура экспертных систем

Чтобы проводить экспертизу, компьютерная программа должна быть способна решать задачи посредством логического вывода и получать при этом достаточно надежные результаты. Программа должна иметь доступ к системе фактов, называемой базой знаний. Программа также должна во время консультации выводить заключения из информации, имеющейся в базе знаний. Некоторые экспертные системы могут также использовать новую информацию, добавляемую во время консультации. Экспертную систему, таким образом, можно представлять состоящей из трех частей:

1. База знаний (БЗ).

2. Механизм вывода (МВ).

3. Система пользовательского интерфейса (СПИ).

База знаний — центральная часть экспертной системы. Она содержит правила, описывающие отношения или явления, методы и знания для решения задач из области применения системы. Можно представлять базу знаний состоящей из фактических знаний и знаний, которые используются для вывода других знаний. Утверждение «Джон Ф. Кеннеди был 35-м президентом Соединенных Штатов» — пример фактического знания. «Если у вас болит голова, то примите две таблетки цитрамона» — пример знания для вывода. Сама база знаний обычно располагается на диске или другом носителе.

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

Когда экспертной системе задается вопрос, механизм вывода выбирает способ применения правил базы знаний для решения задачи, поставленной в вопросе. Фактически, механизм вывода запускает экспертную систему в работу, определяя какие правила нужно вызвать и организуя к ним доступ в базу знаний. Механизм вывода выполняет правила, определяет, когда найдено приемлемое решение и передает результаты программе интерфейса с пользователем. Когда вопрос должен быть предварительно обработан, то доступ к базе знаний осуществляется через интерфейс с пользователем.

Интерфейс — это часть экспертной системы, которая взаимодействует с пользователем. Как правило, пользователи мало знают об организации базы знаний, поэтому интерфейс может помочь им работать с экспертной системой даже, если они не знают, как она организована. Интерфейс может также объяснить пользователю, каким образом экспертная система выводит результат.

В настоящем издании мы ограничимся лишь организацией БЗ и управлением стратегией вывода. Для чего рассмотрим два примера реализации ЭС средствами Турбо-пролога: ЭС, построенную на правилах, и ЭС, построенную на логике.

Экспертная система на правилах

Во всех экспертных системах существует зависимость между входным потоком данных и данными в базе знаний. Во время консультации входные данные сопоставляются с данными в базе знаний. Результатом сопоставления является отрицательный или утвердительный ответ.

В системе, базирующейся на правилах, утвердительный результат является действием одного из продукционных правил. Эти продукционные правила определяются входными данными.

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

Экспертная система также содержит интерпретатор в механизме вывода. Работу этого интерпретатора можно описать последовательностью трех шагов: интерпретатор сопоставляет образец правила с элементами данных в базе знаний; если можно вызвать более одного правила, то интерпретатор использует механизм разрешения конфликта для выбора правила; интерпретатор применяет выбранное правило, чтобы найти ответ на вопрос. Этот процесс интерпретации является циклическим и называется циклом «распознавание-действие».

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

Предположим, что пользователь сообщил множество характеристик собаки в ответ на вопросы экспертной системы. Интерпретатор работает в цикле распознавание-действие. Если характеристики, заданные пользователем, сопоставимы с характеристиками породы собаки, составляющими часть базы знаний, тогда вызывается соответствующее продукционное правило и в результате идентифицируется порода. Затем результат сообщается пользователю. Если порода не идентифицирована, это тоже сообщается пользователю.

Рассмотрим две породы собак, информация о которых содержится в базе знаний. Гончая имеет короткую шерсть, высоту в холке меньше 57 см длинные уши и хороший характер. Датский дог имеет короткую шерсть, низко посаженный хвост, длинные уши, хороший характер и вес более 45 кг.

Из этого описания видно, что обе породы имеют короткую шерсть, длинные уши и хороший характер. Рост гончей меньше 57 см в то время, как ничего не сказано о росте дога. Дог имеет низко посаженный хвост и вес более 45 кг — характеристики отсутствующие для гончей. Описание двух собак в терминах указанных характеристик достаточно, чтобы различить эти две породы, и даже отличить их от любой другой породы в базе знаний.

Следующие продукционные правила могут быть составлены по указанным характеристикам:

dog_is («Гончая»): — it_is («короткоерстная собака»),

positive(«ее»,"высота в холке не более 57 см"),

positive(«у нее»,"длинные уши"),

positive(«у нее»,"дружелюбный характер"), !.

dog_is («Great Dane»): — it_is («длинношерстная собака»),

positive («у нее»,"низко посаженный хвост"),

positive(«у нее»,"длинные уши"),

positive(«у нее»,"дружелюбный характер"),

positive(«ее»,"вес более 45 кг"), !.

Заметим, что в правилах длина шерсти может быть представлена с помощью предиката positive в виде:

positive («у нее»,"короткая шерсть").

Но использование предиката it_is позволяет ограничить «пространство поиска» (количество данных, проверяемых при поиске решения) одним поддеревом древовидной структуры, содержащей информацию о разных породах собак.

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

ЛИСТИНГ

/* Программа: эксперт по породам собак */

/* Это продукционная система, базирующаяся на правилах*/

database

xpositive (symbol, symbol)

xnegative (symbol, symbol)

predicates

do_expert_job

do_consulting

ask (symbol, symbol)

dog_is (symbol)

it_is (symbol)

positive (symbol, symbol)

negative (symbol, symbol)

remember (symbol, symbol, symbol)

clear_facts

goal

do_expert_job.

clauses

/* Систесма пользовательского интерфейса */

do_expert_job : — makewindow (1, 7, 7, «ЭКСПЕРТ ПО ПОРОДАМ СОБАК, 1, 16, 22, 58),

nl, write («* * * * * * * * * * * * * * * * * * * * * «),

nl, write («ДОБРО ПОЖАЛОВАТЬ! «), nl, nl,

nl, write («Проводится идентификация породы «), nl, nl,

nl, write («Отвечайте, пожалуйста, 'да' или 'нет' «),

nl, write («а вопросы о собаке, породу которой «),

nl, write («Вы хотите определить «),

nl, write (« * * * * * * * * * * * * * * * * * * * * * * «),

nl, nl, do_consulting, write («Нажмите любую клавишу»),

nl, readchar (_), removewindow.

do_consulting : — dog_is (X), !, nl, write («Вероятно Ваша собака — «, X,». «),

nl, clear_facts.

do_consulting : — nl, write («Извините, я не смогу помочь Вам!»),

clear_facts.

ask (X, Y) : — write («?: — «, X, « «, Y, «? «), readln (Reply),

remember (X, Y, Reply).

/* Механизм вывода */

positive (X, Y) : — xpositive (X, Y),!.

positive (X, Y) : — not (negative (X, Y)),!, ask (X, Y).

negative (X, Y) : — xnegative (X, Y),!.

remember (X, Y, yes) : — asserta (xpositive (X, Y)).

remember (X, Y, no) : — asserta (xnegative (X, Y)), fail.

clear_facts : — retract (xpositive (_,_)), fail.

clear_facts : — retract (xnegative (_,_)), fail.

/* Продукционные правила */

dog_is («Английский Бульдог») : — it_is («короткошерстная собака»),

positive(«ее»,"высота в холке не более 57 см"),

positive(«у нее»,"низко посаженный хвост"),

positive(«у нее»,"дружелюбный характер"),!.

dog_is («Гончая») : — it_is («короткошерстная собака»),

positive(«ее»,"высота в холке не более 57 см"),

positive(«у нее»,"длинные уши"),

positive(«у нее»,"дружелюбный характер"),!.

dog_is («Немецкий Дог») : — it_is («короткошерстная собака»),

positive(«у нее»,"низко посаженный хвост"),

positive(«у нее»,"дружелюбный характер"),

positive(«ее»,"вес более 45 кг"),!.

dog_is («Американский Фоксхаунд») : — it_is («короткошерстная собака»),

positive(«ее»,"высота в холке не более 77 см"),

positive(«у нее»,"длинные уши"),

positive(«у нее»,"дружелюбный характер"),!.

dog_is («Кокер Спаниель») : — it_is («длинношерстная собака»),

positive(«ее»,"высота в холке не более 57 см"),

positive(«у нее»,"низко посаженный хвост"),

positive(«у нее»,"длинные уши"),

positive(«у нее»,"дружелюбный характер"),!.

dog_is («Ирландский Сеттер») : — it_is («длинношерстная собака»),

positive(«ее»,"высота в холке не более 77 см"),

positive(«у нее»,"длинные уши"),!.

dog_is («Колли») : — it_is («длинношерстная собака»),

positive(«ее»,"высота в холке не более 77 см"),

positive(«у нее»,"низко посаженный хвост"),

positive(«у нее»,"дружелюбный характер"),!.

dog_is («Сенбернар») : — it_is («длинношерстная собака»),

positive(«у нее»,"низко посаженный хвост"),

positive(«у нее»,"дружелюбный характер"),

positive(«ее»,"вес более 45 кг"),!.

it_is («короткошерстная собака «) : —

positive(«это»,"короткошерстная собака «),!.

it_is («длинношерстная собака «) : —

positive(«это»,"длинношерстная собака «),!.

/* Конец программы */

Экспертные системы, базирующиеся на фактах

В экспертных системах, базирующихся на фактах, база знаний состоит из утверждений в виде предложений логики предикатов. Такие предложения могут группироваться, образуя БД Турбо-Пролога. Правила могут либо описывать данные, либо управлять процессом внутренней унификации Турбо-Пролога.

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

Интерпретатор выполняет различные функции внутри системы на основе следующей схемы:

1. Система имеет предложения в базе знаний, которые управляют поиском и сопоставлением. Интерпретатор сопоставляет эти предложения с элементами данных в базе данных.

2. Если может быть вызвано более одного правила, то система использует возможности Турбо-Пролога для разрешения конфликта. Следовательно, пользователю/программисту не нужно рассматривать потенциально возможные конфликты.

3. Система получает результаты унификационного процесса автоматически, поэтому они могут направляться на нужное устройство вывода информации.

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

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

rule (1,"Собака","Гончая",[1,2,3,4]).

rule(2,"Собака","Немецкий Дог",[1,5,3,4,6]).

cond(1,"короткошерстная").

cond(2,"высота в холке не более 57 см").

cond(3,"длинные уши").

cond(4,"дружелюбный характер").

cond(5,"низко посаженный хвост").

cond(6,"вес более 45 кг").

Заметьте, что в каждом предложении типа rule первый аргумент — номер правила, второй аргумент — тип объекта («собака») и третий аргумент — порода собаки. В нашем случае это гончая или дог. Список целых чисел задает номера условий из предложений типа cond (условие). Предложения типа cond содержат все характеристики для любой породы, представленной в базе знаний.

Списки номеров условий служат для хранения множества фактов, согласно которым выбираются предложения типа rule. Интерпретатор в экспертной системе, базирующейся на логике, использует эти номера условий, чтобы делать соответствующий выбор.

Добавление и обновление предложений базы знаний являются простыми операциями (предикаты retract и assert). Экспертные системы, базирующиеся на фактах, легко проектировать, развивать и поддерживать в Турбо-Прологе, так как по мере расширения базы знаний программа не требует модификации. Расширение, прежде всего, заключается в постепенном добавлении новых утверждений.

ЛИСТИНГ

/* Программа: Эксперт по породам собак */

/* Пример экспертной системы, базирующейся на логике */

domains

conditions = integer *

history = integer *

database

/* Предикаты базы данных */

rule (integer, symbol, symbol, conditions)

cond (integer, symbol)

yes (integer)

no(integer)

predicates

/* Предикаты системы пользовательского интерфейса */

do_expert_job

do_consulting

goes (symbol)

clear

/* Предикаты механизма вывода */

go (history, symbol)

check (integer, history, conditions)

Inpo (history, integer, integer, symbol)

do_answer (history, integer, symbol, integer, integer)

goal

do_expert_job.

clauses

/* База знаний (БЗ) */

rule(1, «Собака», «Короткошерстная собака», [1]).

rule(2, «Собака», «Длинношерстная собака «, [2]).

rule(3, «Короткошерстная собака»,"Английский бульдог",[3,5,7]).

rule(4, «Короткошерстная собака»,"Гончая", [3,6,7]).

rule(5, «Короткошерстная собака»,"Немецкий Дог", [5,6,7,8]).

rule(6, «Короткошерстная собака»,"Американский фоксхаунд",

[4,6,7]).

rule(7, «Длинношерстная собака «, «Кокер спаниель», [3,5,6,7]).

rule(8, «Длинношерстная собака «, «Ирландский Сеттер», [4,6]).

rule(9, «Длинношерстная собака «, «Колли», [4,5,7]).

rule(10, «Длинношерстная собака «, «Сенбернар», [5,7,8]).

cond(1,"Короткошерстная").

cond(2,"Длинношерстная").

cond(3,"Высота в холке не более 57 см").

cond(4,"Высота в холке не более 77 см «).

cond(5,"Низко посаженный хвост").

cond(6, «Длинные уши»).

cond(7, «Дружелюбный характер»).

cond(8, «Вес более 45 кг»).

/* Система пользовательского интерфейса */

do_expert_job : —

makewindow(1,7,7,"ЭКСПЕРТ ПО ПОРОДАМ СОБАК", 0, 0, 25, 80),

do_consulting, nl, nl, nl, nl, write («Нажмите любую клавишу»),

readchar (_), exit.

do_consulting: -goes (Mygoal), go ([], Mygoal),!.

do_consulting: -nl, write («Извините, я не смогу Вам помочь»), clear.

do_consulting.

goes (Mygoal) : — clear, clearwindow, nl, nl,nl, nl,

write («ДОБРО ПОЖАЛОВАТЬ»), nl, nl, nl,

write(«Проводится идентификация породы»), nl,

write(«Для того, чтобы начать процесс идентификации,»), nl,

write(«введите слово 'Собака'. «), nl, nl, readln (Mygoal),!.

inpo (HISTORY, RNO, BNO, TEXT): -write («?: — «, TEXT,» ? «),

makewindow(2,7,7,"Для ответа на вопрос", 10,54,7,35),

write(«введите 1, если Ваш ответ 'да' ,»),

write(«введите 2, если Ваш ответ 'нет' ,»),

write(«введите 0, для выхода из системы»), nl,

readint (RESPONSE),

clearwindow, shiftwindow (1),

do_answer (HISTORY, RNO, TEXT, BNO, RESPONSE).

/* Механизм вывода */

go (HISTORY, Mygoal): -rule (RNO, Mygoal, NY, COND),

check (RNO, HISTORY, COND),!,

go ([RNO|HISTORY], NY).

go (_, Mygoal): -not (rule (_, Mygoal,_,_)),!,

nl, write («Вероятно Ваша собака — «, Mygoal,». «), nl, nl, nl.

check (RNO, HISTORY,[BNO|REST]) : —

yes(BNO),!, check (RNO, HISTORY, REST).

check (_,_,[BNO|_]) : — no (BNO),!, fail.

check (RNO, HISTORY,[BNO|REST]): -cond (BNO, TEXT),

inpo (HISTORY, RNO, BNO, TEXT),

check (RNO, HISTORY, REST).

check (_,_,[]).

do_answer (_,_,_,_, 0): — exit.

do_answer (_,_,_, BNO, 1): -assert (yes (BNO)), write (yes), nl.

do_answer (_,_,_, BNO, 2): -assert (no (BNO)), write (no), nl, fail.

clear : — retract (yes (_)), retract (no (_)), fail,!.

clear.

/* Конец программы */

Выбор типа ЭС

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

Итак, если экспертная система, которую Вы хотите сделать, в конечном счете может содержать сотни продукционных правил, то трудно определить эффект от добавления дополнительных правил. В Турбо-Прологе продукционные правила помещаются в программу, и, следовательно, размеры программы увеличиваются по мере добавления правил. Размеры памяти, в конце концов, ограничивают число правил. В этом случае использование системы на правилах становится проблематичным. В то же время, в системе, базирующейся на логике, где база знаний может находиться в файле на диске, ограничения на размеры базы знаний не накладываются. Поэтому система, основанная на логике, в этом случае предпочтительнее.

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

Если быстрота является главным требованием к разрабатываемой экспертной системе, то можно выбрать либо систему на фактах, полностью находящуюся в оперативной памяти, либо систему, базирующуюся на правилах. Обе будут работать хорошо. Если, однако, экспертная система должна содержать большую базу знаний, то у разработчика имеется единственный вариант — система на фактах, находящаяся на диске.

КОНТРОЛЬНЫЕ ВОПРОСЫ

1. Для чего служит и что содержит секция database?

2. Какое имя получает БД по умолчанию.

3. Перечислить достоинства и недостатки использования резидентных БД.

4. Перечислить достоинства и недостатки использования нерезидентных БД.

5. Для чего предназначены и как выполняются стандартные предикаты asserta, assertz, retract, save, consult, readterm, findall.

6. Опишите работу стандартного предиката dir. Приведите примеры.

7. Для каких целей используется стандартный предикат disk? Приведите примеры его использования.

8. Как в Прологе описываются файлы.

9. Привести стандартные предикаты для работы с резидентными БД.

10. Для чего предназначены и как выполняются стандартные предикаты openread, openwrite, openappend, openmodify, readdevice, writedevice, closefile.

ПРАКТИЧЕСКИЕ ЗАДАНИЯ

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

ПоказатьСвернуть
Заполнить форму текущей работой