Скачиваний:
201
Добавлен:
17.06.2016
Размер:
2.69 Mб
Скачать

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

Так как список имеет рекурсивную составную структуру данных, нужны

алгоритмы для его обработки. Главный способ обработки списка - это прос-

мотр его и обработка каждого элемента, пока не будет достигнут конец.

Алгоритму этого типа обычно нужно два предложения. Первое из которых

говорит, что делать с обычным списком (списком, который можно разделить

на голову и хвост). Второе говорит, что делать с пустым списком.

Печать списков

К примеру, нужно напечатать элементы списка, это делается так:

/*Программа CH08EX01.PRO - печать элементов списка*/

domains

list = integer* /*или любой тип, какой вы хотите*/

predicates

write_a_list(list)

clauses

write_a_list([]) /*если список пустой - ничего не делать*/

write_a_list([H|T]) :-

/*присвоить H-голова,T-хвост, затем ...*/

write(H), nl,

write_a_list(T).

goal

write_a_list([1, 2, 3]).

Вот два целевых утверждения write_a_list, описанные на обычном язы-

ке:

Печатать пустой список - значит ничего не делать.

Иначе, печатать список - означает печатать его голову (которая явля-

ется одним элементом), затем печатать его хвост (список).

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

write_a_list([1, 2, 3]).

Оно удовлетворяет второму предложению, при H=1 и T=[2, 3]. Компьютер

напечатает 1 и вызовет рекурсивно write_a_list:

write_a_list([2, 3]). /*это write_a_list(T).*/

Этот рекурсивный вызов удовлетворяет второму предложению. На этот

раз H=2 и T=[3], так что компьютер печатает 2 и снова рекурсивно вызывает

write_a_list с целевым утверждением

write_a_list([3])

Итак, какому предложению подходит это целевое утвержение? Вспомните,

что хотя список [3] имеет всего один элемент, у него есть голова и хвост;

голова 3 и хвост []. Так, что снова целевое утверждение подходит под вто-

рое предложение с H=3 и T=[]. Компьютер печатает 3 и делает рекурсивный

вызов

write_a_list([]).

Теперь видно, что этому целевому утверждению подходит первое предло-

жение. Второе предложение не подходит, так как [] нельзя разделить на го-

лову и хвост. Так, что если бы не было первого предложения, целевое ут-

верждение было бы невыполнимым. А раз так, первое предложение подходит и

целевое утверждение выполняется без дальнейших действий.

Упражнение

Является ли write_a_list хвостовой рекурсией? Будет ли оно таковой,

если два предложения записать в обратном порядке?

Подсчет элементов списка

Рассмотрим, как можно определить число элементов в списке. Что такое

длина списка? Вот простое логическое определение:

Длина [] - 0.

Длина любого другого списка - 1 плюс длина его тела.

Можно ли применить это на компьютере? В Прологе - да. Для этого нуж-

но два предложения:

/* Программа CH08EX02.PRO - длина списка */

domains

list = integer* /* или любой другой тип */

predicates

length_of(list, integer)

clauses

length_of([], 0).:

length_of([_|T], L) :-

length_of(T, TailLength),

L = TailLength + 1.

Посмотрим сначала на второе предложение. Действительно, [_|T] подхо-

дит к любому непустому списку, присвоением хвосту списка значения T. Зна-

чение головы не важно, главное, что оно есть и компютер может посчитать

его за один элемент. Таким образом, целевое утверждение

length_of([1, 2, 3], L)

подходит второму предложению при T=[2, 3]. Следующим шагом будет подсчет

длины T. Когда это будет сделано (не важно как) TailLength будет иметь

значение 2, и компьютер добавит к нему 1 и затем присвоит L значение 3.

Итак, как компьютер выполнит промежуточный шаг? Шаг, в котором опре-

деляется длина [2, 3] при выполнении целевого утверждения

length_of([2, 3], TailLength).

Другими словами, length_of вызывает сама себя рекурсивно. Это целе-

вое утверждение подходит второму предложению с присвоением

[3] в T согласно предложению

TailLength из целевого утверждения в L в предложении

Напомним, что TailLength в целевом утверждении не совпадает с

TailLength в предложении потому, что каждый рекурсивный вызов в правиле

имеет свой собственный набор переменных. Если это не ясно, почитайте раз-

дел по рекурсии в Главе 7.

Итак, целевое утверждение в том, чтобы найти длину [3], т.е. 1, а

затем добавить 1 к длине [2,3], т.е. к 2. И так далее.

Таким образом length_of вызывает сама себя рекурсивно, чтобы полу-

чить длину [3]. Хвост [3] - [] так, что T будет присвоен [], а целевое

утверждение будет в том, чтобы найти длину [] и добавив к ней 1 получить

длину [3].

На сей раз просто. Целевое утверждение

length_of([], TailLength)

удовлетворяет первому предложению, которое присвоит 0 переменной

TailLength. Компьютер добавит к нему 1 и получит длину [3], затем вернет-

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

длину [2, 3] и вернется в вызывающее его предложение. Это начальное пред-

ложение снова добавит 1 и получит длину [1, 2, 3].

Не запутались? Надеемся, что нет. На самом деле так и происходит. (В

этом примере мы пользовались названиями, чтобы показать, что переменные с

одним идентификатором, но из разных предложений или из разных вызовов от-

личаются друг от друга.

/* Замечание: Далее следует пример, а не коды программы*/

length_of([1, 2, 3], L1).

length_of([2, 3], L2).

length_of([3], L3).

length_of([]), 0).

L3 = 0+1 = 1.

L2 = L3+1 = 2.

L1 = L2+1 = 3.

Соседние файлы в папке Документация