Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пролог_лекция_4.doc
Скачиваний:
1
Добавлен:
14.09.2019
Размер:
53.25 Кб
Скачать

Управление выполнением программы на прологе

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

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

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

Методы управления откатом:

Метод отката после неудачи.

Этот метод используется в ситуации, когда нужно получить не один ответ, а все возможные в данной ситуации ответы. Например, если вопрос является внутренней целью, то Турбо Пролог останавливает поиск после первого же успешного вычисления цели. При этом выявляется только первое решение. В методе отката после неудачи обычно используется всегда ложный предикат fail. Хотя, по большому счету, вместо этого предиката можно воспользоваться каким-нибудь заведомо ложным выражением. Например, 1=2 или чем-то в этом роде.

Пример. Теперь давайте напишем предикат, который будет выводить на экран с помощью стандартного предиката write имена всех дочек.

show_names:–

mother(_,Name), /* означивает переменную Name

именем дочки */

write(" ", Name), nl,

/* выводит значение переменной

Name на экран */

fail. /* вызывает откат на место, сохраненное

в стеке точек возврата */

GOAL

write("Имена дочек:"),nl,

show_names.

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

пример с городами

Метод отсечения и отката.

В основе этого метода лежит использование комбинации предикатов fail (для имитации неудачи и искусственной организации отката) и «!» (отсечение или cut), который позволяет прервать этот процесс в случае выполнения какого-то условия. Предикат «!» всегда истинен и запрещает откат для подцелей, расположенных левее.

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

show_names3(Daughter):–

mother(_,Name), /* означивает переменную Name именем дочки */

write(" ", Name), nl, /* выводит значение переменной Name */

Name=Daughter, /* проверяет совпадение имен дочек Name и Daughter. В случае несовпадения вызывает откат на место, указатель на которое хранится в стеке точек возврата. В случае совпадения, за счет наличия отсечения, завершает поиск и вывод имен дочек */

write("Искомый человек найден!"),!.

Метод повтора, определяемый пользователем.

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

repeat.

repeat:– repeat.

Этот предикат всегда успешен. После выполнения первого предложения процедуры в стек точек возврата запоминается указатель, поскольку имеется еще один альтернативный вариант для него. В теле второго предложения опять вызывается предикат repeat.

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

Пример. Создадим предикат, который будет дублировать символ, введенный пользователем с клавиатуры. Завершиться этот процесс должен, когда пользователь введет некий ключевой символ, о котором мы заранее договоримся, что его появление означает окончание процесса дублирования символов. Для определенности, возьмем в качестве такого символа точку (.).

double_char:–

repeat,

readchar(C), /* читаем символ с клавиатуры в переменную C */

write(C,C), nl,/* выводим на экран значение переменной C */

C=’.’,!, /* сравниваем значение переменной C с символом ‘.’*/

nl, write("Была введена точка. Закончили.").

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

Далее, используя стандартный предикат readchar, осуществляем чтение символа с клавиатуры в переменную C. Посредством встроенного предиката write выводим два экземпляра введенного пользователем символа на экран; стандартным предикатом nl переводим курсор на следующую строку. Затем значение переменной C сравнивается с символом точка (‘.’). Это условие, которое, с одной стороны, обеспечивает откат на первую подцель (предикат repeat), а с другой обеспечивает выход из цикла. Если поступивший с клавиатуры символ отличен от точки, подцель C=’.’ терпит неуспех. Пролог-система осуществляет откат на последнее место, указатель на которое записан в стеке точек возврата. Из подцелей, расположенных левее, альтернативы имелись только у первой подцели (предиката repeat). Все повторяется еще раз. Пользователь вводит символ, он дважды отображается на экране, сравнивается с точкой. Процесс повторяется до тех пор, пока введенный символ не окажется точкой. В этом случае подцель C=’.’ окажется успешной, управление перейдет на подцели, расположенные правее. Предикат nl сдвинет курсор на начало следующей строки, стандартный предикат write выводит на экран сообщение: "Была введена точка. Закончили." — и процесс дублирования символов на этом завершается.

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

GOAL

write("Вводите символы, которые нужно повторить (точка — завершение)"),nl, double_char.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]