Работа со списками в языке Пролог Рекурсия в Прологе

Вид материалаДокументы

Содержание


Шаг рекурсии
Пример: Создадим предикат, который будет вычислять по натуральному числу его факториал N!
Списки в Прологе
Рассмотрим обработку списков
Предикат, позволяющий проверить принадлежность элемента списку
Предикат, позволяющий соединить два списка в один
Подобный материал:
Работа со списками в языке Пролог

Рекурсия в Прологе

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


Пример: Предположим, что в БЗ есть набор фактов, описывающий родственные связи людей через отношение "быть родителем". Предикат родитель имеет два аргумента. В качестве его первого аргумента указывается имя родителя, в качестве второго - имя ребенка. Мы хотим создать отношение "быть предком", используя предикат родитель.


предок(Предок,Потомок):-

родитель(Предок,Потомок). /* предком является родитель */

предок(Предок,Потомок):-

родитель(Предок,Человек),

предок(Человек,Потомок). /* предком является родитель предка */


Любая рекурсивная процедура должна включать в себя базис и шаг рекурсии.

Базис рекурсии - это предложение, определяющее некую начальную ситуацию или ситуацию в момент прекращения. Это предложение часто содержит условие, при выполнении которого происходит выход из рекурсии или отсечение.

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

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


<имя определяемого предиката>:-

[<подцели>],

[<условие выхода из рекурсии>],

[<подцели>],

<имя определяемого предиката>,

[<подцели>].


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

Рекурсия имеет большой недостаток, так как ей может не хватить для работы памяти стека.

Пример: Создадим предикат, который будет вычислять по натуральному числу его факториал N!

fact(1,1). /* факториал единицы равен единице */

fact(N,F):-

N>1, /* убедимся, что число больше единицы */

N1=N-1,

fact(N1,F1), /* F1 равен факториалу числа на 1 меньшего исходного */

F=F1*N. /* N! равен произведению F1 на само число */

Списки в Прологе

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

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

["понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье"]

[1, 2, 3, 4, 5, 6, 7]

['п', 'в', 'с', 'ч', 'п', 'с', 'в']

[]пустой список, т.е. список, не содержащий элементов.

Элементы списка могут быть любыми, в том числе и составными объектами (списками).

В разделе описания доменов списки описываются следующим образом:

DOMAINS

<имя спискового домена>=<имя домена элементов списка>*

Например:

listR = integer* /* список, элементы которого — целые числа */

lists = string* /* список, состоящий из строк */

listL = listI* /* список, элементами которого списки целых чисел */

Последнему примеру будут соответствовать списки вида:

[[1,2,3],[],[4,5,6],[0,-10]]

В Турбо Прологе в отличие от классического все элементы списка должны принадлежать одному домену.

Рекурсивное определение списка: Список — это структура данных, определяемая следующим образом:
  1. пустой список ([ ]) является списком;
  2. структура вида [H|T] является списком, если H — первый элемент списка (или несколько первых элементов списка, перечисленных через запятую), а T — список, состоящий из оставшихся элементов исходного списка.

Принято называть H головой списка, а Tхвостом списка.

Фактически операция "|" позволяет разделить список на хвост и голову или, наоборот, приписать объект (объекты) к началу списка.

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

Например, в списке [1, 2, 3] элемент 1 является головой, а список [2, 3] — хвостом, т.е. [1, 2, 3] = [1|[2, 3]].

Заметим, что хвост этого списка [2, 3], в свою очередь, может быть представлен в виде головы 2 и хвоста [3], а список [3] можно рассматривать в виде головы 3 и хвоста []. Пустой список далее не разделяется.

Чтобы организовать обработку списка, в соответствии с приведенным выше рекурсивным определением, достаточно задать предложение (правило или факт, определяющее, что нужно делать с пустым списком), которое будет базисом рекурсии, а также рекурсивное правило, устанавливающее порядок перехода от обработки всего непустого списка к обработке его хвоста. Иногда базис рекурсии записывается не для пустого, а для одно- или двухэлементного списка.

Рассмотрим обработку списков

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

length([], 0). /* в пустом списке элементов нет */

length([_|T], L) :–

length(T, L_T), /* L_T — количество элементов в хвосте */

L = L_T + 1. /* L — количество элементов исходного списка */

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


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

member(X,[X|_]). /* X — первый элемент списка */

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

member(X,T). /* X принадлежит хвосту T*/

Заметим, что в первом случае, нам неважно, какой у списка хвост, и можно в качестве хвоста указать анонимную переменную. Аналогично, во втором случае, если X принадлежит хвосту, нам не важно, какой элемент первый.

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

Например:

member(X, [1, 2, 3]).

В качестве результата получим список всех элементов списка:

X=1

X=2

X=3

Можно получить по элементу варианты списков, которые могут его содержать. Для этого свободную переменную запишем вторым аргументом предиката, а первым — конкретное значение (member(1, X)).


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

conc([ ], L, L). /* при присоединении пустого списка к списку L получим L */

conc([H|T], L, [H|T1]) :–

conc(T,L,T1). /* соединяем хвост и список L, получаем хвост результата */

Заметим, что этот предикат также можно применять для решения других задач:
  1. Для проверки получится ли при объединении двух списков третий.

conc([1, 2, 3], [4, 5], [1, 2, 5]).  No
  1. Для разбиения списка на подсписки.

conc(X, Y, [1, 2, 3]).  Получим четыре решения:

X=[], Y=[1, 2, 3]

X=[1], Y=[2, 3]

X=[1, 2], Y=[3]

X=[1, 2, 3], Y=[]
  1. Для поиска элементов, находящихся левее и правее заданного элемента. Например, если нас интересует, какие элементы находятся левее и, соответственно, правее числа 2, можно задать следующий вопрос:

conc(L, [2|R], [1, 2, 3, 2, 4]).  Получим два решения:

L=[1], R=[3, 2, 4]

L=[1, 2, 3], R=[4]


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

n_element([X|_],1,X).

n_element([_|L],N,Y):–

N1=N–1,

n_element(L,N1,Y).