Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программы классификации 49 9 Программа классифи...doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
944.13 Кб
Скачать

3.4.4. Принадлежность элемента списку

Требуется определить предикат:

member(X,L).

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

member(X, [X| _ ]).

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

Напомним, что знаком подчерка здесь обозначена анонимная переменная, значение которой в данном предложении неважно.

Работа предиката следующая: при каждом рекурсивном вызове список будет короче, т.к. аргументом заголовка является структура [ _ | T ], а в рекурсивном вызове - список Т. Рекурсивные вызовы продолжаются до тех пор, пока не будет достигнуто совпадение значения переменной X с головой списка Т, или если окажется пустой список, тогда предикат завершается ложно, так как для пустого списка нет своего правила.

Обычно этот предикат используют в двух вариантах:

1) Переменная X неконкретизирована, а список L конкретизирован. В этом случае в результате работы предиката переменная X конкретизируется элементом списка L. Используя механизм возвратов, можно переменной X последовательно придавать значения всех элементов списка L.

2) Переменная X и список L конкретизированы. Значением предиката будет “истина”, если X совпадает с одним из элементов списка, и "ложь" в противном случае.

Для проверки на принадлежность можно также использовать и отношение «удалить»: некоторый X принадлежит списку, если его можно оттуда удалить:

member( X, L ) :- away( X, L, _ ).

При этом элемент X вовсе не удаляется из списка L, достаточно только того, чтобы существовала возможность его удаления, т.е. предикат away должен закончиться на «истинно».

3.4.5. Сцепление (конкатенация) списков

Определим предикат

conc( L1, L2, L3 ).

Объединяемые списки задаются первыми двумя аргументами L1 и L2. Список L3 представляет собой конкатенацию этих списков.

Для определения отношения конкатенация отделим два случая:

(1) Если первый список пуст, то второй и третий представляют собой один и тот же список (базовое правило):

conc( [ ], L , L ).

(2) Если первый аргумент не пуст, то он имеет голову и хвост, т.е. [X|L1]. Его сцепление со списком L2 – список [X|L3], где список L3 получен после сцепления списков L1 и L2, т.е.

conc( [ X | L1 ], L2, [ X | L3 ] ) :– conc( L1, L2, L3 ).

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

Познакомимся с механизмом рекурсивного вызова процедур на конкретном примере. Пусть требуется выполнить конкатенацию списков [a,b] и [c,d]. Таким образом, первый и второй аргументы конкретизированы, а третий еще нет. Начинается прямое прохождение рекурсии: по правилу (2):

conc([a, b], [c, d], L3),

где L3 есть конкатенация элемента а и некоторого списка L31, т.е. L3=[a | L31]. Этот предикат вызывает

conc([b], [c, d], L31),

где L31=[b | L311], далее вызывает

conc([ ], [c, d], L311).

Здесь L3, L31, L311 - сгенерированные переменные, обозначающие еще неконкретизированные списки.

По базовому правилу (1) последнее отношение удовлетворяется, если L311 конкретизируется списком [c,d]. Теперь начинается обратное прохождение рекурсии, при котором конкретизируются списки L1,L:

conc([ ], [c, d], [c, d]), возвращает

conc([b], [c, d], [b, c, d]), возвращает

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

Таким образом, базовое правило имеет декларативный и процедурный смысл. Декларативный аспект был указан ранее. Процедурное значение этого правила в том, что оно дает ограничение рекурсии и конкретизирует последнюю сгенерированную переменную (результат).

Отношение конкатенации можно использовать многими способами. Например, его можно применять в обратном направлении для разбиения заданного списка на две части:

Goal conc( L1, L2, [ a, b, c ]).

L1 = [ ]

L2 = [ a, b, c ]

L1 = [ a ]

L2 = [ b, c ]

L1 = [a, b]

L2 = [ c ]

L1 = [a, b, c ]

L2 = [ ]

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

Goal conc( L1, [ c | L2], [ a, b, c, d, e, f ] ).

L1 = [ a, b ]

L2 = [ d, e, f ]

Далее, мы сможем, например, удалить из некоторого списка L1 все, что следует за тремя последовательными вхождениями элемента z в L1 вместе с этими тремя z:

Goal L1 = [ a, b, z, z, c, z, z, z, d, e, f ] ,

conc( L2 , [z, z, z | _ ], L1).

L1 = [ a, b, z, z, c, z, z, z, d, e, f ] ).

L2 = [ a, b, z, z, c ] ).

Через конкатенацию можно определить отношение «последний элемент списка» last так, чтобы элемент E являлся последним элементов списка L:

last( E, L ) :- conc( _ , [ E ], L ).

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

member( X, L ):-

conc( _ , [ X | _ ], L ).

В этом предложении сказано: «X принадлежит L, если список L можно разбить на две части таким образом, чтобы элемент X являлся головой второго их них».

Рассмотрим теперь отношение «подсписок» (sub_list). Это отношение имеет два аргумента – список L и список S такой, что S содержится в L в качестве подсписка. Так, отношение

sub_list( [ c, d, e ], [a, b, c, d, e, f ])

имеет место, а отношение

sub_list( [ c, e ], [a, b, c, d, e, f ])

нет. Его можно сформулировать так:

(1) L можно разбить на два списка L1 и L2 и

(2) L2 можно разбить на два списка S и L3. Поскольку в данном случае неважно, какой останется L3, вместо него поставим анонимную переменную

Поэтому вышеприведенную формулировку можно выразить на Прологе так:

sub_list( S, L ):-

conc( L1, L2, L ),

conc( S, _ , L2 ),