- •7. Списки в Прологе
- •Определение и унификация
- •Упражнения на унификацию
- •Рассмотрим ещё несколько примеров
- •7.2 Операции на списках
- •7.2.1 Поиск элемента в списке
- •7.2.2 Создание списков
- •7.2.2.1 Простейший случай
- •7.2.2.2 Получение списка из двух существующих списков
- •7.2.2.3 Получение списка по условию
- •7.2.2.4 Вычеркивание элементов списка
- •7.2.2.5 Замена элементов списка
- •7.2.2.6 Проверка, является ли аргумент списком.
- •7.2.2.7 Подсчет количества элементов в списке.
- •7.2.2.9 Вывод списка на дисплей.
- •7.3 Задачи на списках
- •7.3.1 Нахождение путей в связанном неориентированном графе
- •7.3.2 Синтаксический анализатор на дка
7.2 Операции на списках
7.2.1 Поиск элемента в списке
Обычный способ использования списков состоит в том, чтобы сохранять в них информацию и затем последовательно находить эту информацию, во время выполнения программ.
Реализуем такой алгоритм.
Проверяем первый элемент в списке и, если нет совпадения, повторяем этот процесс на остальной части списка. Такое повторение делается с помощью рекурсии, причем поиск останавливается, когда мы или находим специфический элемент в начале списка или когда пройден весь список.
Чтобы вычленить первый элемент списка, нам следует воспользоваться оговоренным ранее способом, т.е. таким разбиением
[Head|Tail]
что и составит основу поиска.
Для примера, назвав нашу процедуру membr, проверим, находятся ли яблоки в списке
[груши, помидоры, яблоки, виноград].
Во-первых, мы решили бы задачу, если бы знали, что целевой элемент является первым в списке, т.е.,
membr(Item, [Item|Rest]). /* - целевой элемент совпадает с головой списка */
Иначе – он должен быть головой нового списка, полученного из исходного выбрасыванием первого элемента (поскольку он нам не подошел) и с новым списком мы начинаем поиск заново.
membr(Item, [BadHead|Tail]):- membr(Item, Tail).
Итак мы получили программу, состоящую из двух предложений.
membr(Item, [Item|Rest]).
membr(Item, [BadHead|Tail]):- membr(Item, Tail).
Давайте посмотрим на нашем примере, как работает алгоритм:
? - membr(яблоки, [груши, помидоры, яблоки, виноград]).
В первом предложении, если Item связать с яблоки, то это значение не совпадет с первым элементом списка груши, следовательно неудача и переходим ко второму предложению.
Тогда надо разбить список следующим образом:
BadHead = груши
Tail = [помидоры, яблоки, виноград]
что по второму правилу дает нам следующую цель запроса:
membr(яблоки, [помидоры, яблоки, виноград]).
Снова мы видим не соответствие яблоки и помидоры, и снова приходится образовывать новый список, отбрасывая «плохую голову», т.е.,
BadHead = помидоры
Tail = [яблоки, виноград]
И вот теперь новая цель: membr(яблоки, [яблоки, виноград]).
наконец удовлетворяет наш запрос.
7.2.2 Создание списков
7.2.2.1 Простейший случай
Чтобы организовать список, например Z, добавлением спереди к списку Y элемента X, надо написать: Z=[X|Y]
Например, возьмем существующий список, скажем List1, и попробуем создать новый список List2, но с новой головой, например атомом prolog:
List2 = [prolog|List1]
И если List1 это список [lisp, c, pascal, basic],то теперь мы получаем список List2, такой: [prolog, lisp, c, pascal, basic],
С помощью унификации это можно проиллюстрировать так:
?- qq(List2) = qq([prolog |[lisp, c, pascal, basic]]).
List2 = [prolog, lisp, c, pascal, basic]
7.2.2.2 Получение списка из двух существующих списков
Воспользуемся встроенным предикатом append, который берет три параметра. Первые два - списки. Предикат использует эти два списка, чтобы произвести третий список, объединяя первые два, присоединяя первый список к голове второго списка.
Например,
?- append([a, b, c], [один, два, три], Rезультат).
Rезультат = [a, b, c, один, два, три]
Теперь попробуем придумать программу, реализующую алгоритм append. Чтобы не путать нашу процедуру со стандартной, её имя запишем как appnd,
Итак, мы сначала пройдем первый список до конца, просматривая каждый элемент и запоминая его. Это мы должны сделать рекурсивно, чтобы потом возвращаясь из рекурсии добавлять эти элементы ко второму списку спереди, но в обратном порядке. Таким образом, последний элемент в первом списке станет первым добавленным ко второму списку.
Надо только оговорить начальное условие добавления
appnd ([],List, List).
Теперь основное работающее правило. Оно перемещает текущий (первый) элемент первого списка на первое место списка резутата.
appnd([Head|Tail], List2, [Head|Result]):-
appnd(Tail,
List2, Result).
Посмотрим работу программы на таком примере
? - appnd([a, b, c], [один, два, три], Rезультат).
Для детализации вызовем trace.
[trace] 5 ?- appnd([a, b, c], [один, два, три], Rезультат).
Call: (6) appnd([a, b, c], [один, два, три], _G672) ? creep
Call: (7) appnd([b, c], [один, два, три], _G757) ? creep
Call: (8) appnd([c], [один, два, три], _G760) ? creep
Call: (9) appnd([], [один, два, три], _G763) ? creep
Exit: (9) appnd([], [один, два, три], [один, два, три]) ? creep
Exit: (8) appnd([c], [один, два, три], [c, один, два, три]) ? creep
Exit: (7) appnd([b, c], [один, два, три], [b, c, один, два, три]) ? creep
Exit: (6) appnd([a, b, c], [один, два, три], [a, b, c, один, два, три]) ? creep
Rезультат = [a, b, c, один, два, три].
Итак, используя второе предложение мы сначала уменьшаем запрос до
appnd ([b, c], [один, два, три], Результат)
затем до
appnd ([c], [один, два, три], Результат)
и наконец получаем
appnd ([], [один, два, три], Результат).
что унифицируется по первому предложению
appnd([],[one,two,three],[one,two,three]).
Так как это факт - он заканчивает рекурсию. Тем самым заканчивается последний рекурсивный вызов, что в итоге позволяет вернуться назад, заканчивая и всю рекурсия с получаемым результирующим списком.
