Предотвращение перебора с возвратами
Система Prolog автоматически выполняет перебор с возвратами, если это необходимо для достижения цели. Автоматический перебор с возвратами представляет собой полезный принцип программирования, поскольку он освобождает программиста от необходимости явно обеспечивать в программе перебор с возвратами. С другой стороны, неуправляемый перебор с возвратами может вызвать снижение эффективности программы. Поэтому иногда возникает необходимость управлять перебором с возвратами или даже предотвращать его. Такую задачу в языке Prolog можно выполнить с помощью оп&ратора отсечения.
Вначале рассмотрим в качестве примера поведение простой программы, выполнение которой связано с некоторым ненужным перебором с возвратами, и определим те моменты, в которые перебор с возвратами бесполезен и приводит к снижению эффективности.
Рассмотрим двухступенчатую функцию, показанную на рис. 5.1. Соответствующую зависимость между X и Y можно определить с помощью приведенных ниже правил.
Правило 1. Если X < 3, то Y = 0.
Правило 2. Если 3 =< X и X < б, то Y = 2.
Правило 3. Если б =< X, то Y = 4.
Эти правила можно записать на языке Prolog в виде бинарного отношения f( К,У)следующим образом:
f( X, О) :- X < 3, % Правило 1 f ( X, 2) :- 3 =< X, X < 6. I Правило 2 f( X, 4} :- 6 =< X. % Правило 3
Y
4 • *
г щ-------- &
_.—■—i—-6 i ■—•—■—■—-
з e x
Рис. 5.1. Двухступенчатая функция
В данной программе, безусловно, предполагается, что перед вызовом на выполнение отношения f ( X, Y) переменная X уже конкретизирована значением какого-либо числа, поскольку это требуется для операций сравнения.
Проведем два эксперимента с этой программой. Каждый эксперимент выявит некоторые источники неэффективности в программе, и мы устраним каждый из этих источников последовательно с помощью механизма отсечения.
Эксперимент 1
Проанализируем, что произойдет при выполнении следующего вопроса:
?- i ( 1, У) , 2 < Y.
При выполнении первой цели, £( 1, Y), переменная Y конкретизируется значением 0. Поэтому вторая цель принимает следующий вид:
Lt; 0
Данная цель не достигается, поэтому не достигается и весь список целей. Причина этого очевидна, но, прежде чем принять, что данный список целей не достижим, система Prolog проверяет с помощью перебора с возвратами еще два бесперспективных варианта. Подробная трассировка этого процесса показана на рис. 5.2.
Рис. 5.2. Трассировка выполнения, при которой в точке, обозначенной как "Оператор отсечения", уже известно, что цели, определенные правилами 2 и 3, на достижимы
Часть I. Язык Prolog
Приведенные выше три правила, касающиеся функциональной зааисимости f, являются взаимоисключающими, поэтому может быть достигнуто не больше одной заданной в них цели. Но система Prolog, в отличие от программиста, не имеет информации о том, что после достижения цели, заданной одним правилом, нет смысла пытаться использовать другие правила, поскольку попытка их достижения неизбежно окончится неудачей. Б примере, приведенном на рис. 5.2, известно, что цель правила 1 будет достигнута в точке, обозначенной как "Оператор отсечения". В этот момент необходимо явно сообщить системе Prolog, что не нужно выполнять перебор с возвратами, чтобы предотвратить осуществление бесполезных действий. Эту задачу можно решить с использованием механизма отсечения. Оператор отсечения записывается как восклицательный знак (!) и вставляется между целями как своего рода псевдоцель. Рассматриваемая программа, переоформленная с использованием операторов отсечения, принимает вид
f ( Xf О) :- X < 3, ! .
f j X, 2) :- 3 =< X, Х< 6, ! .
£( X, 4) : - 6 =< X.
Теперь символ ! предотвращает перебор с возвратами в тех точках, в которых он появляется в программе. Если после этого будет задан следующий вопрос:
?- £( 1, Y;, 2 < у.
система Prolog сформирует такую же левую ветвь трассировки выполнения, как показано на рис. 5,2. Выполнение данной ветви окончится неудачей при обработке цели 2 < 0. Затем система Prolog предпринимает попытку осуществить перебор с возвратами, но не сможет пройти точку в программе, обозначенную символом !, поэтому альтернативные ветви, которые соответствуют правилам 2 и 3, так и не будут сформированы.
Новая программа, составленная с использованием операторов отсечения, в целом является более эффективной, чем первоначальная версия, без этих операторов. Если выполнение программы должно окончиться неудачей, новая программа в целом распознает это быстрее, чем первоначальная.
На основании этого можно сделать вывод, что введение в рассматриваемую программу операторов отсечения позволило повысить ее эффективность. Но если в данном примере будут удалены операторы отсечения, программа все равно выработает такие же результаты; просто она, возможно, будет затрачивать больше времени. В данном случае введение операторов отсечения привело лишь к изменению процедурного значения программы и не повлияло на результаты ее работы. Тем не менее ниже будет показано, что использование операторов отсечения способно также повлиять и на результаты.