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

Волченков Логическое программирование язык пролог 2015

.pdf
Скачиваний:
12
Добавлен:
12.11.2022
Размер:
4.14 Mб
Скачать

Пример 3.6. Печатаются «в столбик» квадраты чисел натурального ряда, пока очередной квадрат не превысит число 1000:

печать_квадратов :– N = 1, квадрат(N, R), write(R), nl,

условие(R), !.

квадрат(N, R) :– R is N^2.

квадрат(N, R) :– N1 is N+1, квадрат(N1, R).

условие(R) :– R > 1000.

UDR-метод (метод «user defined repeat»)

Есть некоторое безвозвратное действие(X), вырабатывающее значение выходного параметра X. Это действие должно многократно повторяться, пока значение параметра X не станет удовлетворять некоторому условию. Для того, чтобы действие стало возвратным, используется встроенный предикат repeat/0. Его семан-

тику поясняет следующее «определение»: repeat.

repeat :– repeat.

Подцель repeat записывается перед подцелью действие(X) в правой части правила, реализующего цикл.

Пример 3.7. Печатаются «в столбик» вводимые пользователем слова, пока не будет введено слово finish:

печать_слов :– repeat, read(W), write(W), nl, условие(W). условие(W) :– W == finish.

Подцель repeat при первом вызове срабатывает без всяких условий. После невыполнения условия осуществляется возврат к этой подцели. Согласно 2-му правилу создаётся новая подцель repeat, которая опять срабатывает без каких-либо условий. И т.д.

4. Встроенный предикат определения новой операции

На предыдущей лекции было отмечено, что кроме структур, для записи которых используются скобки, например f(a, b, c), в языке Пролог используются так называемые бесскобочные структуры инфиксного типа, например a f b. Это бинарные (двухаргументные) бесскобочные структуры. К этому же типу можно условно отнести унарные (одноаргументные) бесскобочные структуры: a f и f a.

51

Использование таких структур облегчает чтение многих запи-

сей, например вместо знает(Джон, Мери) и человек(Сократ) бо-

лее естественны и понятны записи: Джон знает Мери и Сократ человек.

Для инфиксной структуры в языке Пролог есть специальное название: операция. Функтор такой структуры называется оператором, аргументы структуры – операндами.

Когда выражение языка содержит несколько операций, необходимо знать, в каком порядке они применяются.

Пусть, например, @ и # – операторы каких-то бинарных операций над числами. Как понимать следующую запись:

2 # 3 @ 4 @ 5 # 6?

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

:- op(700, xfy, #). :- op(600, yfx, @).

В директивах применяется встроенный предикат Пролога op/3, все аргументы которого – входные параметры. 1-й аргумент – значение приоритета определяемой операции, 2-й аргумент – спецификация ассоциативности, 3-й аргумент – знак оператора.

Значение приоритета операции важно только по отношению к значениям приоритета других операций: меньше (выше) или больше (ниже).

Значения второго аргумента означают следующее: yfx – левоассоциативная операция,

xfy – правоассоциативная операция, xfx – неассоциативная операция,

yf и fy – допускающие вложенность

(например: not not true),

xf и fx – не допускающие вложенности

(например: Джон человек человек).

Таким образом, в выражении 2 # 3 @ 4 @ 5 # 6 операции применяются в следующем порядке: ( 2 # ((( 3 @ 4 ) @ 5 ) # 6)).

52

На следующей лекции будут рассмотрены ещё несколько типов встроенных предикатов, а также популярные в Прологе рекурсивные методы программирования. Для более детального знакомства с этими вопросами автор рекомендует книги Дж. Стобо [5] и Дж. Малпаса [6].

53

Лекция 4

Основы программирования на Прологе (продолжение)

В данной лекции, в дополнение к тому, что рассматривалось в предыдущей лекции, представлены ещё две категории встроенных предикатов: 1) для преобразования структур данных и 2) для про-

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

метод исходящей рекурсии.

1. Встроенные предикаты преобразования структур

Рассмотрим только четыре из множества таких предикатов.

Предикат name/2.

Данный предикат преобразует атомоподобный терм (атом, число или строку) в список кодов символов, из которых этот терм состоит. Или наоборот, список кодов – в атомоподобный терм. Например:

?– name(ася, X). →

X = [1072, 1089, 1103]

?– name(X, [97, 98, 98,

97]). → X = abba

Предикат functor/3.

Данный предикат анализирует структуру произвольного вида (1-й аргумент): определяет её имя (2-й аргумент) и её арность (3-й аргумент). Или наоборот – синтезирует структуру произвольного вида (1-й аргумент) по известному имени (2-й аргумент) и арности (3-й аргумент). Например:

?– functor(f(a, b, c), X, Y).

X = f, Y = 3

?– functor(X, f, 3). →

X = f(_, _, _)

54

Предикат arg/3.

В отличие от предыдущих предикатов этот предикат работает только «в одну сторону»: его спецификация arg/(i, i, o). Для заданной структуры произвольного вида (2-й аргумент) по номеру ее компонента (1-й аргумент) находится значение этого компонента (3-й аргумент). Например:

?– arg(2, f(a, b, c), X). ?– arg(2, [a, b, c, d], X).

X = b

X = [b, c, d]

«В обратную сторону» предикат arg/3 работать не может – по значению компонента структуры нельзя находить его номер.

Предикат =../2.

Данный предикат преобразует структуру произвольного вида (1- й аргумент) в список (2-й аргумент). Или наоборот – список в структуру. Голова списка (1-й элемент) – имя структуры, хвост списка (2-й элемент) – компоненты структуры. Таким образом, длина списка на единицу больше числа компонентов структуры. Примеры:

?– f(a, b, c) =.. X. ?– X =.. [f, a, b, c].

X = [f, a, b, c]

X = f(a, b, c)

2. Примеры предикатов обработки списков

Список, как уже было отмечено, является наиболее используемой структурой данных Пролога. Поэтому в разных реализациях Пролога (в разных Пролог-системах) почти всегда присутствуют встроенные предикаты обработки списков: member/2, append/3, reverse/2, sort/2 и т.д. Но иногда их приходится определять. Приведем определения этих предикатов без комментариев. (Автор оставляет слушателям или читателям возможность самостоятельно изучить их семантику непосредственно по определениям.)

55

Предикат member/2:

member(H, [H|_]).

member(H, [_|T]) :– member(H, T).

Предикат append/3:

append([], L, L).

append([H|T1], L, [H|T2]) :– append(T1, L, T2).

Предикат reverse/2:

reverse([], []).

reverse([H|T], R) :– reverse(T, R1), append(R1, [H], R).

Предикат sort/2:

sort([], []). % Сортировка «вставкой» sort([H|T], S) :– sort(T, T1), insort(H, T1, S).

insort(X, [], [X]).

insort(X, [H|T], [X, H|T]) :– X @=< H, !.

% @=< – сравнение не чисел, а структур insort(X, [H|T], [H|T1]) :– !, insort(X, T, T1).

3. Встроенные предикаты проверки типов

Часто в программах на Прологе приходится проверять такие, например, условия: «Означена или нет данная переменная»; «Является ли данный терм числом»; «Является ли данный терм неатомоподобным термом – структурой (compound)» и т.п.

Для ответов на указанные вопросы служат встроенные предикаты проверки типов. Приведем спецификацию шести таких предикатов без комментариев.

atom/1, atomic/1, number/1, var/1, nonvar/1, compound/1.

Их назначение очевидно – смысл предикатов следует из их названий.

Встроенные предикаты, рассмотренные в предыдущей и настоящей лекциях, составляют далеко не полный их список. Даже

56

если не учитывать специфические, характерные для конкретной реализации средства, «базовый» их набор (имеющийся в наличии любой реализации) существенно шире представленного множества. Разумеется, полное описание всего списка встроенных предикатов выходит за рамки данного лекционного курса.

4. Методы исходящей и входящей рекурсии

Вспомним хорошо известное рекурсивное определение факториала:

0!

= 1

 

n!

= n*(n-1)!

n = 1,2,...

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

К примеру, в Паскале:

i:=0; b:=1;

while i <> n do begin i:=i+1;

b:=b*i;

end;

r:=b

Здесь n – входной параметр, r – выходной параметр, значение которого – факториал заданного числа.

На Прологе можно решить эту задачу абсолютно так же, «в стиле операторного программирования»:

factorial(N, N, R, R) :- !. factorial(I, N, B, R) :-

I1 is I + 1,

B1 is B * I1, factorial(I1, N, B1, R).

57

Здесь, как и в программе на Паскале, две переменные (N и R) выступают в роли входного и выходного параметров, а две другие переменные – в роли «счётчика» (I) и в роли «накопителя произведения» (B).

Предикат factorial/4 в качестве второго и четвёртого аргумента имеет входной и выходной параметры, а в качестве первого и третьего аргумента – «счетчик» и «накопитель».

В базовом состоянии рекурсии, когда значения счётчика и входного параметра сравняются, значение накопителя «перебросится» в последний аргумент (первое правило определения).

Отметим отличия программы на Прологе от программы на Паскале:

в Прологе нет оператора присваивания, так как «единожды означившись» переменная в пределах одного утверждения уже не может получить другое значение (конструкция i := i+1 заменяется на конструкцию I1 is I+1 и т.п.);

все утверждения, представляющие программу на Прологе,

состоят только из структур, интерпретируемых как предикаты, которые имеют только два значения – истину и ложь.

Теперь о рекурсии. Хотя данная программа имитирует операторный «итерационный» метод, в ней, как и практически в любой программе на Прологе, используется рекурсия. Без рекурсии Пролог просто немыслим!

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

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

В качестве примера применения метода исходящей рекурсии рассмотрим также вычисление факториала:

58

factorial(0, 1) :- !. factorial(N, R) :- N1 is N - 1,

factorial(N1, R1), R is R1 * N.

Еще более характерный пример различия методов входящей и исходящей рекурсии можно продемонстрировать на определениях предиката reverse/2. Два аргумента этого предиката – это исходный и «реверсированный» список (список, у которого первый элемент – это последний элемент исходного списка, второй элемент – предпоследний элемент исходного списка и т.д.).

По методу исходящей рекурсии работает следующее определение:

reverse1([], []). reverse1([H|T], R):-

reverse1(T, T1), append(T1, [H], R).

В этом определении используется предикат append/3, работающий весьма медленно, что делает данное определение нежелательным.

Более эффективно работает другое определение, сделанное по методу входящей рекурсии:

reverse2(L1, L2):- reverse2(L1, [], L2).

reverse2([], L, L). reverse2([H|T], B, R):-

reverse2(T, [H|B], R).

Хотя это определение несколько длиннее предыдущего, работает оно эффективнее, так как в нём уже нет предиката append/3.

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

59

5. Примеры решения задач

Встроенные предикаты арифметики и ввода-вывода

С арифметикой в Прологе не всё обстоит благополучно. Например, в Примере 3.6 лекции 3 (печать квадратов натуральных чисел) используется предикат X is N^2. И вместо целых чисел иногда, по- чему-то, печатаются десятичные числа (22^2 = 483.999999…).

Пришлось заменить возведение в степень умножением:

X is N * N. Но можно решить эту проблему и по другому: ввести округление: Y is N^2, cint(Y, X).

Задача 1. Определить предикат cint/2 – округления произвольного десятичного числа.

Решение:

cint(X, Y) :- Z is sign(X), A is abs(X), I is ip(A), F is fp(A),

F > 0.5, !,

Y is Z * (I+1). cint(X, Y) :- Y is ip(X), !.

Можно ли сократить это определение? Автор предлагает слушателям и читателям подумать над этим вопросом.

Задача 2. Посимвольный ввод. Рассмотрим определение:

vv(W):-get0(X), vv1([X],L,X), name(W,L).

vv1(L, L, 122). % 122 – код символа z vv1(B, L, X):- get0(Y), vv1([Y|B], L, Y).

Рассмотрим вызов и ввод нескольких символов:

| ?- vv(W).

|: tyh875654435566778&^%%$%#$#yz

(В конце нажата клавиша Enter.) Каким будет возвращённое значение переменной W? Как изменить определение vv/1, чтобы посимвольно были напечатаны все введённые символы?

60

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