Механизм вывода в системе Пролог, страница 5

При определении рекурсивного предиката следует най­ти граничные условия и способ использования рекурсии. Пер­вое граничное условие указывает на совпадение элемента Х с головой списка и успешное прекращение поиска, что и опре­деляется первым правилом. Второе граничное условие встре­чается, когда второй аргумент предиката "принадлежит" явля­ется пустым списком. Каждый раз при поиске происходит ре­курсивное обращение к предикату "принадлежит" и новая цель формируется для хвоста, т.е. всегда более короткого списка.

Теперь можно написать программу:

принадлежит(Х,[Х|_]).
принадпежит(Х,[_|Z) :- принадлежит(Х,Z).

На запрос

принадлежит(седло,[руль,рама,колесо,седло])

следует утвердительный ответ

Анализ списка неизвестной длины. Воспользуемся предикатом "принадлежит" для анализа принадлежности дета­лей, узлов велосипеда заданным узлам. Пусть имеется сле­дующая БД:

узел([велосипед, рама, руль, седло, колесо]).
узел([колесо, обод, покрышка, камера, спицы, втулка]).

детали(Х,Y) :- узел([Х|Z]), принадлежит(Y,Z).

принадлежит(Х,[Х]_|).

принадлежит(Х,|_Z) :- принадлежит(Х,Z).

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

Выбор общих элементов в двух списках. Для выпол­нения этой процедуры достаточно потребовать, чтобы некото­рый элемент принадлежал разным спискам, т.е.

общий(Х,Y,Z) :- принадлежит(Z,Х), принадлежит(Z,Y).

Выбор n-го элемента списка. Для выбора n-го элемен­та списка достаточно указать, что первый элемент списка име­ет номер 1, т.е. на языке Пролог это выглядит следующим об­разом:

n(1 ,Элемент,[Элемент|_]).

Затем нужно определить рекурсивное правило, которое расчленяет список на элементы,. параллельно вычитая из за­данного номера единицу до тех пор, пока не выполнится гра­ничное условие, т.е. счетчик станет равным единице (первое правило). Сказанное описывается следующим правилом:

n(М, Элемент, [_|Список]) :- Счетчик = N - 1, n(Счетчик, Элемент, Список).

На запрос

n(3,X,[a,b,c,d,e])

получаем

Х=с

Приведем полную программу:

n(l, E, [E|_]).

n(N,E,[_|L]) :- C = N – 1, n(C, E, L).

Рассмотрим последовательность действий, выполняе­мых интерпретатором с целью получения решения. Здесь 3 - запрос, Н - найдено правило, У - унификация.

З  n(З,E,[a,b,c,d]).

Н  n(l,E,[E|_]). ошибка унификации

З  n(З,E,[a,b.c,d]).

Н  n(N,E,[_|L]) :- C = N - l, n(C,E,L).

У  n(З,E,[a|b,c,d] :- 2 = З - l, n(2,E,[b,c,d])).

З  n(2,E,[b,c,d]).

H  n(l ,E,[E|_]). ошибка унификации: 2 !¹

З  n(2,E,[b,c,d]).

H  n(N,E,[_|L]) :- С = N - 1, n(С,Е, L).

У  n(2,E,[b,c,d]) :- 1 = 2 - 1 ,n(1 ,E,[c,d]).

З  n(l,E,[c,d]).

H  n(l,E,[E|_]). успех: 1=1

У  n(l,c,[c|d]).

следовательно, n(l, c,[c,d])

следовательно, n(2,c,[b,c,d])

следовательно, n(3,c,[a,b,c,d])

solution: E = c

Соединение двух списков. Как отмечалось выше, соедине­ние двух списков с целью получения третьего осуществляется с помощью предиката " | ". Так, если

Х = [а,b,с], Y = [d,e,f] и Z = [X]Y], то Z=[a,b,c,d,e,f].

Процедуру присоединения списка Х к списку Y для по­лучения списка Z интерпретатор рассматривает следующим образом:

присоединить [а, b, с]         к  [d, e, f], чтобы иметь [a,b,c,d,e,f]

это значит

присоединить [b,с] к [d,e,f], чтобы иметь [b,c,d,e,f]

это значит

присоединить [с] к [d,e,f], чтобы иметь [c,d,e,f]

это значит

присоединить [] к [d,e,f], чтобы иметь [d,e,f]                           (A)

значит присоединить [ ] к [d,e,f], получая [d,e,f|

значит присоединить [с] к [d,e,f|, получая [c,d,e,f]

значит присоединить [b,с] к [d,e,f], получая [b,c,d,e,f]

значит присоединить [а,b,с] к [d,e,f], получая [a,b,c,d,e,f]

Строка (А) выражает факт соединения пустого списка с некоторым списком L, в результате чего получается список L. Это записывается так:

присоединить ([ ],L,L).

В строках, предшествующих строке А, удаляют голову первого списка и присоединяют ко второму, чтобы получить третий список:

присоединить ([X|L1],L2, [X|L3]) :- присоединить(L1,L2,L3).

Функция присоединения является важной и в Прологе называется append. Напишем программу:

append([ ], L, L).

append([X|Ll], L2, [X|L3]) :- append(Ll,L2,L3).

На запрос append([a,b,c], [d,e,f], L) получим L = [a,b,c,d,e,f].

На запрос append(L, [d,e,f|, [a,b,c,d,e,f]) получим L = [a,b,c].

На запрос append([a,b,c], L, [a,b,c,d,e,f]) получим L = [d,e,f].

Другими словами, функция append позволяет составлять и расчленять список. Так, на запрос append(Ll, L2, [a,b,c,d])

получим

L1

L2

[ ]

[a,b,c,d]

[a]

[b,c,d]

[a,b]

[c,d]

[a,b,c]

[d]

[a,b,c,d]

[ ]

Здесь находятся все возможные списки, с помощью ко­торых можно сформировать третий список.

Выбор наименьшего и наибольшего элементов спи­ска. Можно сказать, что наименьший элемент списка, состоя­щего из одного элемента, является этим элементом, т.е.

меньший([Х], X).

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

меньший([Х], X).

меньший([Х, Y|Z], R) :-
X <= Y, меньший([Х|Z], R);
X > Y, меньший([Y|Z], R).

Здесь сравниваются два первых элемента списка и меньший из элементов добавляется к оставшемуся подсписку.

На запрос меньший([b,c,a,e], X) получим Х=[а].

Выбор наибольшего элемента списка производится ана­логично, только нужно заменить знак ">" знаком "<" и наобо­рот.

Перевод одного списка термов в другой. Предполо­жим, требуется перевести (дословно) предложение с англий­ского языка на русский. Заданы два списка слов с английскими и соответствующими им русскими словами. Чтобы остановить перевод предложения, воспользуемся тем, что пустой список переводится в пустой:

перевести([],[]).

очевидно, что этот факт действителен для всех языков. Далее запишем следующее правило: