Скачиваний:
28
Добавлен:
22.05.2015
Размер:
164.35 Кб
Скачать

Лабораторная работа 4 Рекурсивные правила

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

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

Ряды

Пример 1. Рассмотрим рекурсию на примере предиката ряд/2, который выводит значения членов арифметической прогрессии, начиная с начального члена From, до конечного члена UpTo, с шагом равным +1:

class predicates

ряд : (integer From, integer UpTo).

Опишем этот предикат в виде двух предложений:

clauses

ряд(N,K) :- N>K,!. % условие останова рекурсии

ряд(N,K) :- write(N),nl,ряд(N+1,K). % рекурсия

Рекурсивный вызов

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

Второе предложение содержит рекурсивный вызов.

Сам предикат ряд/2 является аналогом итеративного цикла (цикла с выходом по условию), который используется в императивных языках программирования.

Исходный текст консольного приложения выглядит так:

implement main

    open core, console

constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".

class predicates ряд : (integer From, integer UpTo).

clauses classInfo(className, classVersion).

ряд(N,K) :- N>K,!. ряд(N,K) :- write(N," "), ряд(N+1,K).

run():-    init(),    ряд(2,8),    _=readchar().

end implement main

goal

   mainExe::run(main::run).

Запустив это программу, Вы можете увидеть ряд чисел от 2 до 8, разделённых пробелом.

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

run():-

   init(),

   ряд(2,8),

   _=readchar(),

   fail;

   write("Программа завершена"),

   _=readchar().

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

Совершенно иной результат будет в случае, когда рекурсивный предикат ряд/2 не использует отсечение. Тогда он станет недетерминированным, так как будет иметь бесконечно много альтернатив:

implement main

    open core, console

constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".

class predicates ряд : (integer From, integer UpTo) nondeterm.

clauses classInfo(className, classVersion).

ряд(N,K) :- N>K. ряд(N,K) :- write(N," "),ряд(N+1,K).

run():-    init(),    ряд(2,8),    _=readchar(),    fail;    write("Программа завершена"),    _=readchar().

end implement main

goal

   mainExe::run(main::run).

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

Задание 1. В примере 1 поменяйте местами предикаты в теле правила так, чтобы рекурсивный вызов шёл вначале, а вывод на экран после него:

ряд(N,K) :- N>K,!.

ряд(N,K) :- ряд(N+1,K), write(N," ").

Объясните результаты работы программы.

Задание 2. На основе примера 1 разработайте программу вывода членов арифметической прогрессии с шагом, задаваемым третьим аргументом Step предиката ряд/3:

class predicates

ряд : (integer From, integer UpTo, integer Step).

Пример 2. Разработаем рекурсивный предикат, который выводит заданное количество Count членов арифметической прогрессии, начиная с начального члена From, с шагом равным +1:

class predicates

ряд : (integer From, integer Count).

Опишем этот предикат в виде двух предложений:

clauses

ряд(_,0) :- !. % условие останова рекурсии

ряд(N,Count) :- write(N," "),ряд(N+1,Count-1). % рекурсия

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

Исходный текст консольного приложения выглядит так:

implement main

    open core, console

constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".

class predicates ряд : (integer From, integer Count).

clauses classInfo(className, classVersion).

ряд(_,0) :- !. ряд(N,Count) :- write(N," "),ряд(N+1,Count-1).

run():-

   init(),

   ряд(10,4),

   _=readchar(),

   fail;    write("Программа завершена"),

   _=readchar().

end implement main

goal

   mainExe::run(main::run).

Обратите внимание, что в рекурсивном вызове счётчик цикла уменьшается на единицу.

Задание 3. Разработайте рекурсивный предикат геом/3, который выводит заданное количество Count членов геометрической прогрессии, начиная с начального значения From и с множителем прогрессии Coef. Исходные данные From, Count и Coef должны вводиться с клавиатуры. Следует объявить предикат геом/3 с нижеуказанными доменами:

class predicates

геом : (real From, integer Count, real Coef).

Соседние файлы в папке Лабораторные работы