Лабораторная работа 2 Поиск с откатом
Настоящая лабораторная работа выполняется в консольном приложении и является базовой в изучении работы машины логического вывода Пролога, основанного на механизмах унификации (unification) и поиска с возвратом (backtracking).
Цвета автомобилей
Пусть имеется множество марок автомобилей {КАМАЗ, Toyota, BMW} и множество возможных цветов кузова автомобиля {зелёный, красный}. Представим эти множества в Прологе и осуществим поиск по различным критериям. Опишем множество автомобилей в виде предиката с именем автомобиль/1 с одним аргументом, принадлежащим домену string. Для этого создадим новый консольный проект, скомпилируем его и вставим в файл main.pro перед разделом clauses новый раздел с именем class facts, и объявим в нём недетерминированный предикат:
class facts
автомобиль : (string).
Множество автомобилей зададим в разделе clauses:
clauses
автомобиль("КАМАЗ").
автомобиль("Toyota").
автомобиль("BMW").
Аналогичным способом добавим в файл main.pro объявление и определение предиката цвет/1. После указанных модификаций файл main.pro должен иметь такой исходный код:
implement main
open core, console
constants
className = "com/visual-prolog/main".
classVersion = "$JustDate: $$Revision: $".
c
Объявление
предикатов
автомобиль : (string).
цвет : (string).
clauses
classInfo(className, classVersion).
а
Предложения
предиката
автомобиль/1
автомобиль("Toyota").
автомобиль("BMW").
ц
Предложения
предиката
цвет/1
цвет("красный").
run():-
Тело цели –
предиката run()
succeed().
end implement main
goal
mainExe::run(main::run).
Следует заметить, что предложения одного предиката необходимо группировать, то есть располагать друг за другом и не смешивать с предложениями другого предиката.
Пример 1. Осуществим поиск автомобиля "Toyota". Для этого удалим из цели предикат succeed(), который Visual Prolog автоматически вставляет при создании проекта, и модифицируем цель следующим образом:
run():-
init(),
Первое предложение
write("Есть такой автомобиль"),
_=readchar();
Второе предложение
_=readchar().
Запустите программу и исследуйте её поведение, указывая различные марки автомобиля, даже отсутствующие в программе. Всплывающее окно предупреждений о неиспользуемом предикате можно игнорировать.
Как Пролог осуществляет поиск на дереве решений и запоминает пройденные на нём пути? Рассмотрим по шагам работу механизма поиска с возвратом, используя классическую стековую модель. При вызове первого предложения предиката run/0 Пролог запоминает в стеке адрес второго предложения (адрес возврата). Адрес возврата нужен для того, чтобы в случае неудачи при выполнении первого предложения можно было бы быстро перейти к выполнению второго предложения, вытолкнув его адрес из стека.
В первом предложении вызывается недетерминированный предикат автомобиль("Toyota"). При его выполнении в стек заносится адрес возврата для предиката автомобиль/1, в результате чего в стеке находятся уже два адреса возврата. Далее возможны два исхода:
Если хотя бы одно из предложений предиката автомобиль/1 содержит аргумент "Toyota", то вызов этого предиката будет успешен и Пролог выполнит отсечение. Отсечение удалит из стека все адреса возврата, помещённые туда при выполнении предиката run/0 (а их всего два), тем самым лишая Пролог возможности перейти к выполнению остальных предложений предиката автомобиль/1 и run/0 в случае отката. После этого выполнятся две процедуры – вывод на экран сообщения "Есть такой автомобиль" и ожидание ввода с клавиатуры.
Если ни одно из предложений предиката автомобиль/1 не содержит аргумент "Toyota", то вызов этого предиката будет неуспешен и произойдёт откат назад. Для этого из стека автоматически выталкивается адрес возврата и Пролог переходит к выполнению второго предложения – выводит на экран сообщение "Нет такого автомобиля" и ожидает ввод с клавиатуры. При этом в стек ничего не помещается, так как третьего предложения в предикате run/0 нет.
Именно так и работает механизм поиска с откатом. Подведём итог:
если предикат имеет больше одного предложения, то при его выполнении в стеке запоминается адрес возврата к очередному предложению;
если происходит откат назад, то Пролог переходит к адресу, который выталкивается из стека;
отсечение удаляет из стека все адреса возврата, помещённые туда в ходе выполнения предиката, в теле которого стоит это отсечение, а также всех его подцелей.
Пример 2. Для нахождения всех автомобилей целевой предикат должен быть таким:
run():-
init(),
автомобиль(X),
write("Найден автомобиль: ",X), nl,
fail;
write("Конец поиска"),
_=readchar().
Запустите программу и исследуйте её поведение.
Обратите внимание, что для получения всех решений мы убрали отсечение и вместо него поставили предикат fail, который принудительно вызывает откат назад.
Как работает механизм поиска с откатом в этом случае? При вызове предиката автомобиль(X) Пролог помещает в стек адрес возврата и находит первое решение X="КАМАЗ", после чего выводит сообщение на экран и выполняет предикат fail. Этот предикат вызывает откат назад и из стека выталкивается адрес возврата. Выполняется второе предложение предиката автомобиль/1. При этом в стек заталкивается новый адрес возврата и находится новое решение X="Toyota", выводится сообщение на экран и происходит принудительный откат назад. Опять из стека выталкивается адрес возврата и выполняется последнее третье предложение в результате чего X="BMW". Однако в отличие от предыдущих предложений в стек ничего не помещается и принудительный откат назад выталкивает из стека адрес второго предложения предиката run/0. На экран выводится сообщение "Конец поиска" и выполняется ожидание ввода с клавиатуры.
Пример 3. Для нахождения всех комбинаций автомобиль‑цвет целевой предикат должен быть таким:
run():-
init(),
автомобиль(Авто), цвет(Цвет),
write(Авто," - ",Цвет), nl,
fail;
write("Конец перебора"),
_=readchar().
end implement main
goal
mainExe::run(main::run).
Запустите программу и исследуйте её поведение. Запомните порядок вывода решений:
КАМАЗ - зелёный
КАМАЗ - красный
Toyota - зелёный
Toyota - красный
BMW - зелёный
BMW - красный
Конец перебора
Обратите внимание, что вызов двух недетерминированных предикатов
автомобиль(Авто), цвет(Цвет), fail
с откатом осуществляет перебор всех цветов для каждого автомобиля. Это объясняется тем, что с помощью отката Пролог будет перебирать все цвета до тех пор, пока они не закончатся. И только после этого предикат цвет(Цвет) завершится неудачей, что вызовет откат назад и Пролог найдёт новый автомобиль для которого перебор всех цветов повторится заново. Получается своеобразный цикл в цикле.
Задание 1. Найдите все комбинации цвет‑автомобиль.
Задание 2. Найдите все комбинации автомобиль‑автомобиль и объясните полученный результат.