
- •Предисловие
- •Глава 1 Введение в Пролог
- •Глава 2 Синтаксис и унификация
- •2.1. Синтаксис термы
- •Константы
- •Переменные
- •Область действия переменных
- •Сложные термы, или структуры
- •Синтаксис операторов
- •Синтаксис списков
- •Синтаксис строк
- •Утверждения
- •Запросы
- •Ввод программ
- •2.2 Унификация
- •Глава 3 Арифметические выражения
- •3.1. Введение
- •3.2. Арифметические выражения
- •3.3. Арифметические операторы
- •3.4. Вычисление арифметических выражений
- •3.5. Сравнение результатов арифметических выражений
- •Глава 4 Рекурсия
- •4.1. Стратегия «разделяй и властвуй»
- •Пример 4.1.1
- •Фаза разбиения
- •Фаза решения задачи
- •4.2. Восходящая стратегия
- •4.3. Рекурсия и эффективность
- •Пример 4.3.2
- •Глава 5 Структуры данных
- •5.1. Списки списковая форма записи
- •А на запрос
- •Некоторые стандартные целевые утверждения для обработки списков
- •5.2. Бинарные деревья представление бинарных деревьев
- •Бинарное дерево на рис.5.2.1 имеет левое поддерево
- •Представление множеств с помощью бинарных деревьев
- •Будем называть линейным представление такого вида, как на рис.5.2.3, и сбалансированным - такое, как на рис.5.2.2.
- •Левому поддереву соответствует отсортированный список
- •Глава 6 Операторы
- •6.1. Операторы и структуры синтаксис операторов
- •Свойства операторов
- •6.2. Позиция операторов
- •6.3. Приоритет операторов
- •6.4. Ассоциативность операторов
- •6.5. Спецификаторы
- •6.6. Операторы объявления
- •6.7. Пример: вычисление многочленов
- •6.8. Системные операторы
- •Глава 7 Механизм возврата и процедурная семантика
- •7.1. Механизм возврата
- •7.2. Пример: задача поиска пути в лабиринте
- •Целевое утверждение не удается согласовать с первым утверждением
- •Если мы введем
- •Если затем мы введем
- •7.3. Обработка фактов с помощью механизма возврата
- •7.4. Предикат repeat
- •7.5. Декларативная и процедурная семантики
- •Характеристики процедуры
- •7.6. Вопросы эффективности
- •Глава 8 Отсечение
- •8.1. Почему используют отсечение?
- •8.2. Использование отсечения
- •Выдав запрос
- •Обратившись к системе с запросом
- •8.3. Ловушки отсечения
- •Глава 9 Встроенные предикаты
- •9.1. Встроенные предикаты
- •9.2. Обновление базы данных Пролога
- •Добавление и удаление утверждений
- •Считывание утверждений в базу данных
- •Предикаты сохранения и восстановления
- •9.3. Особенности ввода и вывода чтение символов
- •Запись символов
- •Считывание термов Предикаты
- •Запись термов
- •9.4. Обработка файлов
- •Редактирование программ на прологе
- •Распечатка предикатов
- •Глава 10 Модули. Пример разработки системы
- •10.1. Модули
- •Пример 10.1.1
- •10.2. Пример разработки системы: деревья решений
- •Описание дерева решений
- •Разработка системы
- •Целевое утверждение
- •Глава 11 Отладка
- •11.1. Трассировка
- •Включение и выключение механизма трассировки
- •Необязательные параметры трассировки
- •Предикаты трассировки
- •Режимы трассировки
- •11.2. Установка контрольных точек
- •Возможные действия в контрольной точке
- •Включение и выключение режима контрольных точек
- •Необязательные параметры режима контрольных точек
- •Предикаты для работы с контрольными точками
- •Режимы, связанные с контрольными точками
- •Основные отладочные предикаты
- •Статистическая информация
10.2. Пример разработки системы: деревья решений
В данном разделе мы разработаем систему для построения и обхода дерева решении. На рис. 10.2.1 показан фрагмент дерева решений, позволяющий выбрать нужную персональную ЭВМ среди компьютеров, выпущенных в 1986 г. Вершины дерева решений содержат вопросы вида «Должна ли персональная ЭВМ быть портативной ?» или заключения «Наиболее подходящей является портативная персональная ЭВМ фирмы IBM». В зависимости от отпета на вопрос дуги, выходящие из вершины, определяют, какой должен быть задан следующий вопрос или какое будет получено заключение. Так, если на вопрос «Портативная ?» (см. дерево решений на рис. 10.2.1) мы ответим «да», то попадаем в вершину «Карманная ?». При ответе «нет» дуга направляет нас в узел «IBM-совместимая ? ».
Рис. 10.2.1. Дерево решений
Описание дерева решений
Создать описание дерева решений позволяют факты вида
верш(Верш_ид, Вопрос, Дуги).
где Верш_ид - идентифицирует вершину. Вопрос - это вопрос или решение, а Дуги - список дуг, выходящих из вершины. Вⲓпрос определяется списком строк, каждая из которых имеет тип «строка». Дуга представляется термом дуга(Отв,Верш_ид), означающим, что при ответе Отв следует перейти к вершине с идентификатором Верш_ид. Вершина «Портативная ?» (см. рис. 10.2.1) .записывается следующим образом:
верш(1,["Портативная ?'."], [дуга (да,2),дуга(нет,3)]).
Мы присвоили идентификаторы 1, 2 и 3 вершинам «Портативная ?», «Карманная ?» и «IBM-совместимая?» соответственно.
Дерево решений не должно иметь циклов. Например, дерево
не является правильным деревом решений.
Разработка системы
Мы будем разрабатывать систему в два этапа. Сначала попытаемся создать удобный интерфейс для пользователя, а затем напишем целевые утверждения для построения и обхода дерева решений.
Этап 1: интерфейс. Спроектируем меню возможных действий пользователя. Каждое действие будет осуществляться соответствующим утверждением выполнить.
меню :-
repeat,nl,
write('Хотите ли вы'),nl,
write('1) загрузить дерево'),nl,
write('2) сохранить дерево'),nl,
write('3) добавить вершину'),nl,
write('4) убрать вершину'),nl,
wrile('5) добавить дугу'),nl,
write('6) убрать дугу'),nl,
write('7) просмотреть дерево'),nl,
write('8) совершить обход дерева'),nl,
write('9) стоп'),nl,
write('Введите 1-9'),nl,
write("),nl,
геаl(Вариант),nl,
выполнить(Вариант).
Утверждение выполнить должно закончиться возникновением состояния неудачи для всех вариантов от 1 до 8. чтобы после совершения требуемого действия на экране снова показалось меню. Нужное утверждение выбирается при сопоставлении цели выполнить(Вариант) с головой утверждения.
Дерево загружается с помощью предиката reconsult:
выполнить(1) :-
write('имя файла ?'),
readb(Ф),
nl,write('чтение файла...'),
write(Ф),
reconsult(Ф),
write('...закончилось'),nl,!,fail.
Для того чтобы сохранить дерево, прежде всего надо поместить специальный маркер в конец списка вершин. Если маркер становится аргументом предиката сохр_верш, то данный предикат убирает маркер и попытка доказательства заканчивается успехом. В противном случае сохр_верш записывает вершину в файл и доказательство завершается неудачей. Начинает работать механизм возврата, выбирается следующая вершина, которая сохраняется в том случае, если не является маркером.
выполнить(2) :-
write('имя файла ?'),
readb(Ф),
open (Ф,блок,write),!,
nl,write ('запись в файл...'),
write(Ф),
assertz (верш(-1,-1,-1)),
верш(Вид,В,Дуги),
сохр_верш(Вид,В,Дуги,блок),
write ('...закончилась'),nl,
close(блок),!,fail.
Добавить вершину можно при условии, что ее дуги не ведут к ней самой. Другими словами, добавление вершины не должно приводить к возникновению циклов. Целевое утверждение цикл(Вид,Дуги) доказывается успешно, если одна из Дуг ведет к Вид, и заканчивается неудачей в противном случае. При успешном доказательстве цели цикл определяется и пользователю показывается циклический путь. Целевое утверждение чит_нов_ид вводит новый идентификатор вершины, чит_вопрос вводит вопрос, а чит_дуги вводит список дуг.
Вьполнить(3) :-
чит_нов_ид(Вид),
write('Введите вопрос (в конце
напишите слово ' конец'):'),nl,
чит_вопрос(В),
чит_дуги(Дуги),
not (цикл (Вид, Дуги)),
assertz (верш (Вид, В, Дуги)),!, fail.
Вершина уничтожается следующим образом:
выполнить(4) :-
чит_стар_ид(Вид),
уничт_верш(Вид),!,fail.
Здесь чит_стар_ид(Вид) вводит идентификатор существующей вершины.
Дуга, выходящая из вершины, добавляется в том случае, если при этом не образуется цикл:
выполнить(5) :-
write('Из вершины'),
чит_стар_ид(Вид),
чит_дугу(Дуга),
not(цикл (Вид, [Дуга])),
доб_дугу (Вид,Дуга),!,fail.
Уничтожение дуги:
выполнить(6) :-
чит_стар_ид(Вид),
чит_дугу(Дуга),
уничт_дугу(Вид,Дуга),',fail.
Показ дерева на экране дисплея:
выполнить,(7) :-
чит_стар_ид(Вид),nl,
write ('Просмотр дерева'),nl,
показ_дерева(Вид),!,fail.
Обход дерева:
выполнить(8) :-
чит_стар_ид (Вид),
обход(Вид),!,fаil.
Выход из меню:
выполнить(9) :- nl,write('Дo свидания'),nl,!.
Чтобы сохранить вершину, мы записываем ее в память при условии, что она не является специальным маркером:
coxp_вepш(-l ,-1,-1,_) :- !,
retract(Bepш(-l,-l ,-1).
сохр_верш (Вид, В, Дуги, Б) : -
writeq(Б,верш(Вид,В,Дуги)),
write(Б,'.'),
nl(Б),!,fail.
Циклы в дереве возникают в том случае, если дуга из множества Дуги ведет к вершине, имеющей идентификатор Вид.
цикл (Вид,Дуги) :-
ведет (Вид,Дуги,Р),
write ('Дуги'), write (Дуги) ,nl.
write ('вызывают образование цикла:'),
write(P),nl.!.
Целевые утверждения уничт_верш, доб_дугу, уничт_дугу, обход, ведет определяются на второй стадии разработки. Описание предикатов чит_нов_ид, чит_стар_ид и чит_дуги достаточно очевидно:
чит_нов_ид(Вид) :-
repeat,
write('Идеитификатор новой вepшины ?'),
геаdb(Вид),nl,
несуществ(Вид),nl.
чит_стар_ид(Вид) :-
repeat,
write('Идeнтификaтop вершины ?'),
readb(Вид),nl,
существует(Вид),!.
несуществ(Вид) :-
верш(Вид,_,_),
write ('Такой идентификатор уже есть !'),
nl,
!,fail.
несуществ(_).
существует(Вид) :- верш(Вид, _,_),!.
существует (_) :-
write('Такого идентификатора нет !'),
nl,
!,fail.
чит_дуги(Дуги) :-
repeat,
nl, write('Cколькo всего дуг ?'),
readb(N).nl,
N=0,
чит_н_дyги(N,Дyги).
чит_н_дуги(0, []) :-!.
чит_н_дуги(N, [Дуга | Дуги]) :-
M is N-1,
чит_н_дуг❽(М,Дуги),
write('Дуга'), wite(N),nl,nl,
чит_дугу(Дуга),!.
чит_дугу(дуга(Отв,Вид) :-
nl, write('Oтвeт ?'),
readb(Oтв),nl,
write('K вершине'),
чит_стар_ид(Вид).
Чтобы прочитать заданный вопрос, надо читать все строки на экране до тех пор, пока не встретится строка 'конец'. Для чтения строк мы используем встроенный предикат readline.
чит_вопрос(В) :-
write('>'),
readline(строка),
продолжать (Строка, В).
продолжать('конец',[]) :-!.
продолжать(Строка, (Строка [В]) :-
чит_вопрос(В).
Вопрос выводится на дисплей построчно:
запис_вопр([]) :-!.
запис_вопр([H | T]) :-
nl,write(H),
запис_вопр(Т).
Возможно, пользователю захочется посмотреть структуру строящегося дерева. Если рисовать дерево не сверху вниз, а слева направо, то его можно изобразить на обычном алфавитно-цифровом дисплее.
Дерево решений на экране будет иметь вид:
??
[Форма
мяча ?]
овальная
[Мяч
для игры в регби]
круглая
[размер
?]
большой
[Футбольный
мяч]
маленький
[Крикетный
мяч]
Предположим, задан список дуг. Из каждой дуги начинается свое дерево. Сначала на экране изображается первая дуга:
Отв
[Вид]
Затем, с отступом от нее, рисуются деревья, вырастающие из дуг вершины Вид, и так до тех пор, пока не будет показано все дерево. Процесс повторяется для всех дуг из списка.
Дерево, вырастающее из вершины Вид, показывается, начиная с дуги дуга(??,Вид):
показ_дерево(Вид) :-
показ_дуги([дуга('?? ',Вид)]0,8).
Первым аргументом предиката показ_дуги является список дуг, вторым аргументом - уровень сдвига, а третьим - величина сдвига.
показ_дуги ([],_,_) :-!.
показ_дуги([дуга (Д, Вид) | Дуги |, М,S) :-
МI is M * S,
tab(MI),write(' '),
write (Д),nl,
tab(MI),
write ('_____________'),
write('['),
write (Вид),
write('] '),nl,
пок_дерево(Вид,М,S),
показ_дуги(Дуги,М,S).
пок_дерево(Вид,N,S) :-
верш(Вид,В,Дуги),
М is N + 1,
показ_дуги(Дуги,М,S).
Этап 2: целевые утверждения для построения и обхода дерева решений. При уничтожении вершины мы уничтожаем также все входящие в нее дуги:
уничт_верш(Вид) :-
retract (верш (Вид, В, Дуги)),
уничт_все_вх_дуги (Вид),
nl,write ('вершина уничтожена'),nl.
Чтобы получить доступ ко всем вершинам, помещаем маркер в конец списка вершин и используем механизм возврата:
yнич_все_вх_дуги(Вид) :-
assertz(верш(-l,-1,-1)).
гetraсt(верш(Вид2,В,Дуги)),
уничт2(Вид2,В,Дуги,Вид),!.
уничт2(-1,-1,-1,_).
уничт2(Вид2,В,Дуги,Вид) :-
уничт_список(Дуги,дуга(Д,Вид),Дуги2),
assertz(верш (Вид2,В,Дуги2)),!,
fail.