Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Prolog.docx
Скачиваний:
1
Добавлен:
25.12.2018
Размер:
94.2 Кб
Скачать

Обработка списков

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

Рассмотрим пример:

calc_one(X, add(Y), Z) :- Z is X + Y.

calc_one(X, sub(Y), Z) :- Z is X - Y.

calc_one(X, mul(Y), Z) :- Z is X * Y.

calc_one(X, div(Y), Z) :- Z is X / Y.

calc(X, [], X).

calc(X, [H | T], Z) :- calc_one(X, H, U), calc(U, T, Z).

В предикате calc первый аргумент является аккумулятором (т. е. временным результатом), а третий — окончательным. Использование:

?- calc_one(2, mul(3), N).

N = 6 .

?- calc(2, [mul(3)], N).

N = 6 .

?- calc(2, [mul(3), add(2), div(2)], N).

N = 4 .

?- calc(2, [], N).

N = 2 .

Еще пример (про робота):

% Движение по прямой

move(X1, Y, east, N, X2, Y) :- plus(X1, N, X2).

move(X1, Y, west, N, X2, Y) :- plus(X2, N, X1).

move(X, Y1, north, N, X, Y2) :- plus(Y1, N, Y2).

move(X, Y1, south, N, X, Y2) :- plus(Y2, N, Y1).

% Поворот

cw(north, east).

cw(east, south).

cw(south, west).

cw(west, north).

% Выполнение роботом одной команды

do_one(robot(X1, Y1, D), move(N), robot(X2, Y2, D)) :- move(X1, Y1, D, N, X2, Y2).

do_one(robot(X, Y, D1), rotate(cw), robot(X, Y, D2)) :- cw(D1, D2).

do_one(robot(X, Y, D1), rotate(ccw), robot(X, Y, D2)) :- cw(D2, D1).

% Выполнение роботом списка команд

do(R, [], R).

do(R1, [C | CTail], R2) :- do_one(R1, C, R), do(R, CTail, R2).

Использование:

?- do_one(robot(0, 0, north), move(3), R).

R = robot(0, 3, north) .

?- do_one(robot(2, 4, north), move(5), R).

R = robot(2, 9, north) .

?- do_one(robot(3, 1, east), rotate(cw), R).

R = robot(3, 1, south) .

?- do_one(robot(3, 1, east), rotate(ccw), R).

R = robot(3, 1, north) .

Поскольку списку строятся с конца, использование аккумулятора при построении списка приводит к инверсии. Реверсия списка.

reverse([], R, R).

reverse([H | T], R1, R2) :- reverse(T, [H | R1], R2).

Использование:

?- reverse([1, 2, 3], [], R).

R = [3, 2, 1].

?- reverse([1, 2, 3], [a, b], R).

R = [3, 2, 1, a, b].

Если инверсия не требуется, то можно использовать разностную структуру. Пример:

flatten(leaf(X), [X | P], P).

flatten(branch(L), P, Q) :- flatten_list(L, P, Q).

flatten_list([], P, P).

flatten_list([H | T], P, Q) :- flatten(H, P, R), flatten_list(T, R, Q).

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

?- flatten(leaf(1), P, []).

P = [1].

flatten(leaf(1), P, [a, b]).

P = [1, a, b].

?- flatten(leaf(1), P, Q), flatten(leaf(2), Q, R), flatten(leaf(3), R, []).

P = [1, 2, 3],

Q = [2, 3],

R = [3].

?- flatten(branch([leaf(1), leaf(2)]), P, []).

P = [1, 2].

?- flatten(branch([leaf(1), branch([leaf(2), leaf(3)]), leaf(4)]), P, []).

P = [1, 2, 3, 4].

?- flatten(branch([leaf(1), branch([leaf(2), branch([]), leaf(3)]), leaf(4)]), P, []).

P = [1, 2, 3, 4].

Отсечение

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

?- member(X, [1, 2, 3]). % X = 1 ; X = 2 ; X = 3.

?- member(X, [1, 2, 3]), !. % X = 1.

?- !, member(X, [1, 2, 3]). % X = 1 ; X = 2 ; X = 3.

?- member(X, [a, b]), member(Y, [c, d]). % X = a, Y = c ; X = a, Y = d ; X = b, Y = c ; X = b, Y = d.

?- member(X, [a, b]), !, member(Y, [c, d]). % X = a, Y = c ; X = a, Y = d.

?- member(X, [a, b]), member(Y, [c, d]), !. % X = a, Y = c.

?- member(X, [2, 1, 3]), member(Y, [2, 1, 3]), X > Y. % X = 2, Y = 1 ; X = 3, Y = 2 ; X = 3, Y = 1 ; false.

?- member(X, [2, 1, 3]), member(Y, [2, 1, 3]), !, X > Y. % false.

?- member(X, [2, 1, 3]), !, member(Y, [2, 1, 3]), X > Y. % X = 2, Y = 1 ; false.

likes(alice, john).

likes(mary, john).

likes(john, mary).

?- likes(X, Y), likes(Y, X). % X = mary, Y = john ; X = john, Y = mary.

?- likes(X, Y), likes(Y, X), !. % X = mary, Y = john.

?- likes(X, Y), !, likes(Y, X). % false.

Областью действия отсечения является запрос (конъюнкция целей); поэтому отсечение, встретившееся в правой части правила не воздействует на цели, предшествовавшие запуску этого правила. Однако если предикат имеет несколько предложений, то отсечение воздействует на все предложения. Например:

gcd(A, 0, C) :- !, C = A.

gcd(A, B, C) :- D is A mod B, gcd(B, D, C).

?- gcd(4, 6, 2). % true.

?- gcd(4, 6, 1). % false.

?- gcd(4, 6, N). % N = 2.

?- N = [4, 6, 3, 9], member(X, N), member(Y, N), gcd(X, Y, 1).

N = [4, 6, 3, 9], X = 4, Y = 3 ; N = [4, 6, 3, 9], X = 4, Y = 9 ;

N = [4, 6, 3, 9], X = 3, Y = 4 ; N = [4, 6, 3, 9], X = 9, Y = 4 ; false.

?- N = [4, 6, 3, 9], member(X, N), member(Y, N), gcd(X, Y, 1), !.

N = [4, 6, 3, 9], X = 4, Y = 3.

Правильное положение отсечения важно.

gcd1(A, 0, C) :- A = C, !.

gcd1(A, B, C) :- D is A mod B, gcd1(B, D, C).

?- gcd1(4, 6, 1). % ERROR: mod/2: Arithmetic: evaluation error: `zero_divisor'

Можно выделить три наиболее частых случая использования отсечения:

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

contains(X, [X | _]) :- !.

contains(X, [_ | T]) :- contains(X, T).

?- contains(b, [a, b, c]). % true.

?- contains(b, [a, b, b, b, c]). % true.

?- contains(b, [a, c]). % false.

?- contains(X, [a, b, c]). % X = a.

?- contains(X, [a, b, c]), X = b. % false.

Во-вторых, отсечение используется для того, чтобы остановить поиск в том случае, когда становится ясно, что требуемый результат не может быть получен. В этом случае отсечение как обычно используют вместе со встроенным предикатом fail, который никогда не удовлетворяется. Без использования отсечения обойтись в таких случаях, как правило, невозможно. Например:

unique([]).

unique([H | T]) :- contains(H, T), !, fail.

unique([_ | T]) :- unique(T).

?- unique([a, b, c]). % true.

?- unique([a, b, b]). % false.

?- unique([a, b, X]). % false.

Если отсечение опустить, то результат будет неверным:

unique1([]).

unique1([H | T]) :- contains(H, T), fail.

unique1([_ | T]) :- unique1(T).

?- unique1([a, b, b]). % true.

В-третьих, отсечение используется для того, чтобы остановить поиск в одном направлении и перейти к поиску в другом направлении. В этом случае отсечение напоминает условный оператор из традиционных языков программирования. Например:

count(_, [], 0).

count(X, [X | T], N) :- !, count(X, T, M), succ(M, N).

count(X, [_ | T], N) :- count(X, T, N).

?- count(b, [a, b, c], N). % N = 1 ; false.

?- count(b, [a, b, b, c, b], N). % N = 3 ; false.

?- count(b, [a, c], N). % N = 0 ; false.

Если отсечение опустить, то результат будет неверным:

count1(_, [], 0).

count1(X, [X | T], N) :- count1(X, T, M), succ(M, N).

count1(X, [_ | T], N) :- count1(X, T, N).

?- count1(b, [a, b, b, c], N). % N = 2 ; N = 1 ; N = 1 ; N = 0 ; false.

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