Добавил:
natribu.org Все что нашел в интернете скидываю сюда Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Точно Не проект 2 / Не books / Источник_1

.pdf
Скачиваний:
10
Добавлен:
01.02.2024
Размер:
20.67 Mб
Скачать

320

Глава 6

 

 

Если оператор объявлен со спецификатором xfy, то он является правоассоциативным.

Таблица 6.1 – Операторы языка Пролог

Имя

Спецификатор

Приоритет

:-

xfx

1200

?-

fx

1200

;

xfy

1100

,

xfy

1000

.

xfy

750

=..

xfx

700

=

xfx

700

=\=

xfx

700

is

xfx

700

=:=

xfx

700

<

xfx

700

=<

xfx

700

>

xfx

700

>=

xfx

700

==

xfx

700

\==

xfx

700

yfx

500

+

yfx

500

/

yfx

400

*

yfx

400

div

yfx

400

mod

xfx

300

6.4. Арифметические выражения

Язык Пролог предназначен, главным образом, для программирования задач символьной обработки информации. Тем не менее, в любую пролог систему включаются все обычные арифметические операторы: + (сложение), - (вычитание), * (умножение), / (деление), mod (остаток от деления целых чисел), div (целочисленное деление).

ВПролог входят также все необходимые операторы сравнения чисел:

<(меньше), > (больше), >= (больше или равно), =< (меньше или равно), =\= (не равно), =:= (равенство целочисленных выражений).

Арифметические выражения в Прологе строят из арифметических операторов, числовых переменных, констант и скобок. При этом арифметические выражения представляют собой структуры. Например, выражение 1+2*3 представляется структурой, изображенной на рисунке 6.3. Если пролог-системе задать вопрос

Язык Пролог

321

 

 

? – Х=1+2*3.

то в результате получим Х=1+2*3, а не Х=7.

Рисунок 6.3 – Представление арифметического выражения 1+2*3.

Объясняется это тем, что оператор =выполняет сопоставление и переменной Х ставит в соответствие структуру, изображенную на рисунке 6.3. При этом арифметическое выражение не вычисляется. Для того чтобы заставить пролог-систему выполнить необходимые вычисления, следует применить специальный оператор is. Например,

? – X is 1+2*3.

В этом случае ответ будет Х=7.

Оператор is определен как инфиксный оператор, который в общем виде записывается так:

X is Y.

Здесь Х – или число, или не конкретизированная переменная, а Y – арифметическое выражение. Если Х – число, то вычисляется значение выражения Y, которое сравнивается с X. При равенстве значений X и Y попытка доказательства Х is Y заканчивается успехом. Если Х – не конкретизированная переменная, то вычисленное значение выражения Y приписывается переменной X. Примеры:

?– 3 is 1+2. Yes

?– X is 7-2. X=5

?– 1+2 is 1+2. No

?– X=1, X is X+1. No

Рассмотрим еще один пример. Определим функцию двух переменных f(x, y)=x+y+1:

322

Глава 6

 

 

f(X,Y,F):- F is X+Y+1.

Тогда в ответ на вопрос

? – f(1, 2, F).

будут получен ответ F=4. Однако доказательство целевого утверждения

? – f(X,2,4).

завершится неудачей, так как оператор is “работает” только в направлении справа налево.

Точно так же, как и оператор is, операторы сравнения вычисляют свои аргументы перед выполнением:

?5+3-1>2+1. Yes

Вбольшинстве современных реализаций языка Пролог имеются встроенные предикаты для вычисления элементарных функций: возведение в квадрат (sqr), логарифм (ln), синус (sin), косинус (cos), абсолютное значение (abs) и др.

6.5.Списки и рекурсия

Список – это структура данных, составленная их произвольного числа элементов. Элементы списка отделяются друг от друга запятыми и заключаются в квадратные скобки. Например, список из четырех элементов 'a', 'b', 'c', 'd' на языке Пролог запишется в виде:

['a', 'b', 'c', 'd'].

Список полезно рассматривать как структуру данных, состоящую из двух частей: головы списка и хвоста списка. Для приведенного выше списка элемент 'a' – это голова списка, а подсписок ['b', 'c', 'd'] хвост списка.

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

.(Голова, Хвост)

Представляя список ['a', 'b', 'c', 'd'] в виде структуры, получаем

.(a, . (b, . (c, . (d, [ ] ))))

Язык Пролог

323

 

 

Здесь пара квадратных скобок [ ] обозначает пустой список.

Для представления списка в виде структуры данных, состоящей из головы и хвоста, в Прологе широко используется еще одно обозначение, в котором голова и хвост списка отделяются вертикальной чертой. Например, в записи [H|T] переменная Н – представляет голову списка, а переменная Т – хвост. Применив символ |, список ['a', 'b', 'c', 'd'] можно представить следующими различными способами:

['a','b','c','d']=['a'| ['b','c','d']]=['a', 'b'| ['c','d']]= =['a', 'b', 'c'| ['d']]=['a', 'b', 'c', 'd'| [ ]].

При попытке доказательства целевого утверждения

? – [x1, x2, x3 | x4]=[1, 2, 3, 4].

пролог-система выдаст ответ: х1=1, х2=2, х3=3, х4=[4].

Над списками часто выполняют следующие операции: добавление элемента в список, удаление элемента из списка, объединение списков, поиск элемента в списке.

Наиболее просто определяется предикат, осуществляющий добавление элемента в список:

добавить (X, L, [X|L]).

Здесь Х – добавляемый элемент; L – список, в который добавляется элемент; [X|L] – результирующий список. Таким образом, элемент Х добавляется в начало списка L.

Представление списков в виде головы и хвоста, где хвост, в свою очередь, тоже список, является рекурсивным. Поэтому обработка списков часто выполняется с помощью рекурсивных предикатов. Рекурсивными называют предикаты, в определениях которых содержатся ссылки на самих себя. Примеры простейших рекурсивных предикатов, выполняющих поиск элемента в списке и объединение двух списков, были приведены в §4.1.8. В общем случае все такие определения строятся по следующей схеме [53]:

предикат(…[ ]…). предикат(…[Голова|Хвост]…): –

обработка(Голова), предикат(…[Хвост]…).

Рекурсивные вызовы прекратятся, когда хвост списка окажется пустым списком. В приведенном определении первое утверждение определяет условие выхода из рекурсии, а второе утверждение – правило, предусматривающее при каждом вызове обработку очередного элемента списка и ре-

324

Глава 6

 

 

курсивный вызов определяемого предиката, аргументом которого является хвост списка.

Определим, например, предикат реверс(Х,Y), где список Y представляет инверсную копию списка Х:

реверс([ ], [ ]).

%1

реверс([H|T],Y): – реверс(T,Ys),

%2

append(Ys,[H],Y).

 

Первое утверждение фиксирует условия выхода из рекурсии, a второе – определяет правило преобразования списка Х в список Y. Для того чтобы выполнить инверсию списка [H|T] , необходимо выполнить инверсию хвоста Т и получить список Ys, а затем добавить голову Н в конец списка Ys и получить результирующий список Y. Интересно отметить, что предикат реверс(Х,Y) позволяет не только выполнять преобразование списка Х в список Y, но и осуществлять обратные преобразования. Например, на вопрос

? – реверс(Х, [1, 2, 3]).

будет получен ответ Х=[3, 2, 1].

Удаление элемента Х из списка L можно представить в виде предиката

удалить(X, L, L1),

где L1 – результирующий список. Если Х является головой списка L, то результирующий список L1 – это хвост списка L. Если Х находится в хвосте списка L, то для удаления Х необходимо рекурсивно вызвать предикат удалить, подставив в качестве второго аргумента хвост списка L. Данным правилам будет соответствовать следующее определение [7]

удалить(Х, [X|T], T). удалить(Х, [H|T], [H|T1]): –

удалить(Х, Т, Т1).

Предикат удалить можно использовать и в обратном порядке. Напри-

мер,

? – удалить (1, L, ['a', 'b']). L=[1, 'a', 'b'];

L=['a', 1, 'b'];

L=['a', 'b', 1].

В данном случае выполняется вставка элемента 1 в произвольные позиции списка. В итоге получаются различные списки L, исключив из которых элемент 1, получим список ['a', 'b'].

Язык Пролог

325

 

 

6.6.Управление возвратом (отсечение)

ВПролог имеется встроенный предикат, ограничивающий возвраты.

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

Определим предикат abs(X,Y), присваивающий переменной Y абсолютное значение Х:

Y

X ,

X 0

 

иначе.

 

X ,

Попробуем непосредственно реализовать данное выражение на Прологе, выделив случай Х 0:

% Определение1 abs(X,Y): – X>=0, Y is X;

Y is –X.

Проверим данное определение на целевом утверждении

? – abs(3, -3).

Пролог-система подставит вместо Х значение 3, а вместо Y – значение -3 и попытается выяснить выполнимость условий 3>=0, -3 is 3. Так как второе условие не выполнимо, то пролог-система выполнит возврат и проверит альтернативную версию Y is -X, то есть -3 is -3. В этом случае выполнение предиката is завершится удачей. Следовательно, в ответ на вопрос abs(3,-3) будет получен ошибочный ответ Yes (да).

Попробуем исправить ошибку. Определим предикат abs(X,Y) следующим образом:

% определение 2 abs(X, Y): – X>=0, Y is X;

X<0, Y is –X.

Теперь при ответе на вопрос

? – abs(3, -3).

будет получен ответ No (нет). Определение 2 будет корректно и при других значениях аргументов X и Y. Однако возникает вопрос об его эффективности. В данном определении выполняются две проверки: Х>=0 и Х>0. В то же время ясно, что при выполнении условия X>=0 вторая строка опре-

326

Глава 6

 

 

деления 2 не должна выполняться. Для того чтобы пролог-системе сообщить это, воспользуемся предикатом отсечения. Введем третье определение предиката abs(X,Y):

% определение 3

abs(X, Y): - X>=0, !, Y is X; Y is -X.

Предикат отсечения !всегда успешно выполняется. Использование предиката !требует осторожности, так как после его выполнения невозможен возврат к точкам выбора, порождаемым определением, в котором используется предикат отсечения.

Рассмотрим дерево вывода целевого утверждения ? – abs(3, -3), изображенное на рисунке 6.4. Здесь показано, что определение 3 порождает альтернативы, которым соответствуют две ветви, исходящие из узла ? – abs(3, -3). После успешной проверки условия 3>=0, выполняется предикат отсечения, который стирает альтернативную ветвь определения. Теперь, после неудачной проверки выполнимости утверждения -3 is 3, про- лог-система не имеет возможности вернуться к альтернативной версии и возвращает в качестве результата значение No (нет). Таким образом, в рассмотренном примере предикат отсечения был использован для программирования взаимоисключающих версий определения.

Рисунок 6.4 – Дерево вывода для вопроса ? – abs(3, -3)

Язык Пролог

327

 

 

Предикат отсечения применяется также для прерывания рекурсивных вызовов и устранения бесконечных циклов. В качестве примера рассмот-

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

y

n

y

n 1

 

1

(

x

y

n 1

),

 

yn 1

 

 

2

 

 

(6.1)

y0

1,

 

 

 

 

 

 

 

 

где yn – значение корня на n-ом шаге вычислений, y0=1 – начальное приближение. Вычисления заканчиваются, когда будет достигнута заданная

точность вычислений yn yn 1 . Пусть =10-5. Определим предикат

квадратный_корень(X,Y):

квадратный_корень(X,Y): –

X>0, !, поиск_квадратного_корня(X, Корень, 1), ( Y is Корень ;

Y is -Корень); Х=:=0, Y is 0.

В данном определении учитываются два возможных варианта вычисления корня. Если X>0, то с помощью предиката поиск_квадратного_корня(…) осуществляется поиск значения корня с заданной точностью. Если Х=0, то Y присваивается нулевое значение. Предикат отсечения здесь используется для программирования указанных взаимоисключающих вариантов вычислений. Покажем, как с помощью предиката отсечения прерываются рекурсивные вызовы. Определим для этого предикат

поиск_квадратного_корня(Х, Корень, Приближение).

поиск_квадратого_корня(Х, Корень, Приближение): – Yn is (X/ Приближение+Приближение)/2,

(abs(Yn - Приближение)< 1.0e-5, !, Корень is Yn;

поиск_квадратного_корня(Х, Корень, Yn)).

Здесь переменная Приближение соответствует yn-1 в формуле (6.1). Когда абсолютное значение разности между yn-yn-1 станет меньше 10-5, то с помощью предиката отсечения стирается альтернативная ветвь, соответствующая рекурсивному вызову предиката поиск_квадратного_корня. Переменной Корень присваивается текущее значение Yn, и происходит выход из рекурсии.

В определениях рассмотренных выше предикатов использована группа. Группа – это заключенная в скобки последовательность вариантов, от-

328

Глава 6

 

 

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

Уточним область действия предиката отсечения. Рассмотрим пример. Определим предикат абсолютный_элемент(X,L), который будет иметь истинное значение, если список L будет содержать числовой элемент, абсолютное значение которого равно Х. Приведем два определения этого предиката:

%Определение 1 абсолютный_элемент(X, L): –

элемент(E, L), abs(E, X).

%Определение 2

абсолютный_элемент(X, L): –

элемент(E, L), (E>=0, !, X is E;

X is –E).

Во втором определении предикат abs(E,X) заменен группой, которая представляет его тело. Предикат элемент(E,L) соответствует предикату member(E,L), определенному в §4.1.7. Проверим данные определения на вопросе:

? – абсолютный_элемент(3, [2, -3]).

При использовании первого определения ответ на вопрос будет положительным, а применение второго определения даст отрицательный ответ No (нет). Происходит это из-за того, что предикат отсечения расширил область своего действия. В первом определении он действует локально в пределах определения abs(E,X). Во втором определении областью его действия является весь предикат абсолютный_элемент(X,L). Поясним сказанное с помощью дерева вывода, изображенного на рисунке 6.5.

Как видно из рисунка, предикат “!” отсекает не только альтернативные ветви, порождаемые определением abs(E,X), но и ветви, обусловленные недетерминированным исполнением предиката элемент. Поэтому правильным будет первое определение предиката абсолютный_элемент.

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

Язык Пролог

329

 

 

Рисунок 6.5 – Вывод утверждения абсолютный_элемент(3, [2, -3])

6.7. Отрицание в языке Пролог

Определим предикат не_элемент(X,L), противоположный предикату

элемент(X, L):

не_элемент(X, L): –

элемент (X, L),!,fail; true.

Здесь предикат не_элемент определяется через предикат элемент. Если подцель элемент(X,L) доказуема, то отсечение исключает выбор альтернативы, а fail вызывает неудачу. Иначе считается, что цель не_элемент(X, L) достижима (true – успех). Такое определение предиката не_элемент основано на использовании предположения о замкнутости мира. При этом под миром понимается множество утверждений конкретной программы. Полагается, что в этих утверждениях содержатся все знания о решаемой задаче. Если некоторое утверждение не представлено в программе, то считается, что оно ложно. В этом случае доказательство истинности исходного утверждения подменяется доказательством недоказуемости противоположного утверждения, что не одно и то же. Поэтому рассмотренной схемой

Соседние файлы в папке Не books