Использование предиката присоединить и спецификация деталей

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

 

присоединить([а,b,с], [3,2,1], [а,b,с,3,2,1]).

 

Предикат присоединить наиболее часто используется для создания нового списка в результате конкатенации двух других списков, как в следующем примере:

 

?- присоединить ([alpha,beta],[gamma,delta],X).

X=[alpha, beta, gamma, delta]

 

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

 

?- присоединить(Х,[b,c,d],[a,b,c,d]). Х=[а]

 

Предикат присоединить имеет следующее определение;

 

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

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

 

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

1. Первый элемент первого списка (X) всегда будет и первым элементом третьего списка.

2. Хвост третьего аргумента (L3) всегда будет представлять результат присоединения второго аргумента (L2) к хвосту первого списка (L1).

3. Для присоединения одного списка к другому, о чем шла речь в пункте 2, необходимо использовать предикат присоединить.

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

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

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

Имеются два типа объектов, которые используются для изготовления велосипеда. Это узлы и детали. Каждый узел состоит из некоторого числа деталей, подобно тому как колесо состоит из спиц, обода и ступицы. Детали не имеют еще более мелких частей – они просто соединяются друг с другом, образуя узлы.

Можно представить детали как факты следующим образом:

 

деталь(обод). деталь(спица).

деталь(задняя_рама). деталь(руль).

деталь(шестерни). деталь(болт).

деталь(гайка). деталь(вилка).

 

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

 

узел(велосипед, [колесо, колесо, рама]).

 

Ниже представлена база данных узлов, необходимых для нашего упрощенного велосипеда:

 

узел(велосипед, [колесо,колесо,рама]).

узел(колесо, [спица,обод,ступица]).

узел(рама, [задняя_рама, передняя_рама]).

узел(передняя_рама, [вилка,руль]).

узел(ступица, [шестерни,ось]).

узел(ось, [болт,гайка]).

 

Заметим, что это частное множество утверждений неполностью описывает велосипед. Мы не делаем различия между передней и задней ступицами – обе имеют шестерни! Цепь и педали отсутствуют, и негде сидеть велосипедисту. Не указано также, как соединять детали друг с другом. Это просто перечисление некоторого числа требуемых деталей.

Теперь мы готовы написать программу, которая для заданной части перечислит все детали, необходимые для ее сборки. Если часть, которую мы хотим собрать, является деталью, то для нее ничего больше не требуется. Однако если мы хотим собрать некоторый узел, то необходимо применить этот процесс к каждой составной части узла. Определим предикат часть (X, Y),где X- имя части, a Y – список деталей, необходимых для ее сборки. В первой версии программы мы не будем рассматривать вопрос о количестве деталей каждого типа, необходимых для сборки. Более полная программа будет представлена в гл. 7.

Выход на граничное условие происходит, когда X является деталью. В этом случае X просто возвращается в качестве элементарного списка:

 

часть(Х, [X]):- деталь(Х).

 

Следующее условие связано со случаем, когда X является узлом. Здесь необходимо определить, имеется ли в базе данных соответствующий факт узел, и если такой имеется, то применить предикат частьк каждому элементу списка подчастей. Для выполнения второй из указанных задач используется предикат, названный список_частей.

 

часть(Х,Р):- узел(Х,Подчасти),

список_чаcтей(Подчасти, Р).

 

Предикат список_частейберет список частей (из второго аргумента факта узел, представленного выше) и применяет предикат частьк каждой части в списке. После вызова самого себя, необходимого для обработки хвоста списка, предикат список_частейдолжен склеить полученные списки вместе, используя предикат присоединить:

 

список_частей([Р|Хвост], Полный_список):- часть(Р,Части_головы)

список_частей(Хвост,Части_хвоста)

присоединить(Части_головы, Части_хвоста, Полный_список)

 

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

Существуют две идеи, указывающие, как использовать предикат часть для генерации предложений на английском языке. Во-первых, предложения могут быть представлены в виде иерархических структур: предложение имеет частигруппа_существительного и группа_глагола; группа_существительного состоит из определения и существительного и т. д. Так что любая простая грамматика может быть выражена на языке «частей». Во-вторых, предикат список_частейвсегда обрабатывает элементы списка, представленного его первым аргументом, в порядке слева направо, и его результат (второй аргумент) накапливается в том же порядке. Два указанных свойства предиката часть показывают, что можно использовать тот же метод для генерации предложений по некоторой грамматике. Типичный «узел» в этой грамматике мог бы выглядеть так:

 

узел(предложение,[группа_существительного,группа_глагола]).

узел(группа_существительного,[определение,существительное]).

узел(определение,[thе]).

узел(существительное, [clergyman]).

узел(существительное,[motorcar]).

 

А слова используемой лексики были бы определены как «детали»:

 

деталь(сlеrgуmаn).

деталь(motorcar)

 

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

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

ГЛАВА 4. ВОЗВРАТ И ОТСЕЧЕНИЕ

Давайте подытожим всю информацию, которую мы почерпнули в гл. 1 и 2 о том, что может произойти с целевым утверждением (целью).

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

(а) Может быть найден факт (или заголовок правила), сопоставимый с целевым утверждением. В этом случае мы говорим, что произошло сопоставление цели с утверждением (фактом или правилом) в базе данных. Это место отличается в базе данных маркером и конкретизируются (присваиваются значения) соответствующие переменные, если они не были конкретизированы ранее. Если произошло сопоставление с правилом, то прежде всего необходимо попытаться доказать согласованность подцелей, вводимых этим правилом. Если цель согласуется с базой данных, то предпринимается попытка согласовать следующее целевое утверждение. В используемых нами диаграммах это будет цель, указанная в следующем, нижнем прямоугольнике, на который указывает стрелка. Если исходная цель входит в конъюнкцию, то это будет цель, расположенная в программе непосредственно справа от исходной цели.

(б) В базе данных нет факта (или заголовка правила), сопоставимого с целевым утверждением. В этом случае мы говорим, что попытка доказать согласованность целевого утверждения потерпела неудачу (цель не согласуется с базой данных). Тогда будет предпринята попытка (см. п.2) вновь доказать согласованность целевого утверждения, указанного в прямоугольнике, расположенном выше стрелки. Если исходное целевое утверждение входит в конъюнкцию, то это будет целевое утверждение, расположенное в программе непосредственно слева от рассматривавшегося целевого утверждения.

2. Мы можем сделать попытку вновь доказать согласованность целевого утверждения с базой данных. Для этого прежде всего необходимо попытаться вновь согласовать каждую из его подцелей, при этом стрелка возвращается в некоторую исходную позицию, поднимаясь вверх по странице. Если ни одна из подцелей вновь не может быть согласована каким-либо подходящим образом, делается попытка найти альтернативное утверждение для самой исходной цели. В этом случае необходимо вернуть в исходное (неопределенное) состояние каждую переменную, конкретизированную при выборе предыдущего утверждения. Эти действия мы называем «уничтожением» результатов, полученных ранее при доказательстве согласованности целевого утверждения. Затем возобновляется просмотр базы данных, но начинается этот просмотр с места, отмеченного маркером данной цели. Как и ранее, эта новая цель, выбранная при возврате, может оказаться либо согласованной, либо несогласованной с базой данных. При этом будет иметь место либо шаг (а), либо шаг (б).

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