Списки. Полиморфизм
Упражнение 5. Определите предикат, который:
- заменяет каждый второй элемент списка заданным элементом, не используя счетчик;
- удаляет каждый n-й элемент списка.
В следующей программе определена операция обращения списка, в результате выполнения которой список записывается в обратном порядке. Например, список [1, 2, 3] преобразуется в список [3, 2, 1]. В определении операции используется вспомогательный аргумент — список, в который перекладываются по одному элементы исходного списка.
class predicates reverse: (A*) -> A*. reverse: (A*, A*) -> A*. clauses reverse(L) = reverse(L, []). reverse([], L) = L. reverse([A | L], L1) = reverse(L, [A | L1]). run():- write(reverse([1, 2, 3, 4, 5])), nl, write(reverse([1, 2], [3, 4, 5])), _ = readLine().Пример 6.6. Обращение списка
В классе list имеется предикат reverse/1, который выполняет операцию обращения списка.
В приведенной ниже программе список целых неотрицательных чисел разбивается на два списка. В первый список попадают все четные элементы, а во второй — все нечетные элементы.
class predicates split: (unsigned*, unsigned* [out], unsigned* [out]). clauses split([A | L], [A | L1], L2):- A mod 2 = 0, !, split(L, L1, L2). split([A | L], L1, [A | L2]):- split(L, L1, L2). split([], [], []). run():- split([1, 2, 3, 4, 5, 9, 8, 7, 6], L1, L2), write(L1), nl, write(L2), _ = readLine().Пример 6.7. Разделение на списки четных и нечетных элементов
Следующая программа посвящена двум важным предикатам. Первый из них — это предикат delete, который удаляет первое вхождение элемента в список. Если такого элемента в списке нет, предикат принимает значение ложь. Второй предикат — это предикат select. Предикат можно использовать двояким образом. С одной стороны, он недетерминированно возвращает произвольный элемент списка и список без этого элемента. С другой стороны, он недетерминированно вставляет заданный элемент на произвольное место в заданном списке, так что в результате получается новый список.
class predicates delete: (A, A*) -> A* determ. select: (A, A*, A*) nondeterm (o,i,o) multi (i,o,i). clauses delete(A, [A | L]) = L:- !. delete(A, [B | L]) = [B | delete(A, L)]. select(A, [A | L], L). select(A, [B | L], [B | L1]):- select(A, L, L1). run():- L = delete(4, [1, 2, 3, 4, 5]), write(L), nl, fail; nl, select(A, [1, 2, 3, 4, 5], L), write(A, " - ", L), nl, fail; nl, select(100, L, [1, 2, 3, 4, 5]), write(L), nl, fail; _ = readLine().Пример 6.8. Предикаты delete и select
Как упоминалось выше, элементы списка в языке Visual Prolog должны принадлежать одному и тому же домену. Поэтому в нем нельзя использовать вложенные списки напрямую и строить термы вида [[0], 1, [2, 3, [4, 5, []]]]. Но такие списки можно смоделировать. В следующей программе рекурсивно определяется домен элементов списка, включающий как атомарные элементы, так и вложенные списки. Аргументом функтора list/1 является список элементов исходного домена. Атомы — это термы с функтором at/1. В программе реализуется операция линеаризации списка — приведения его к списку атомарных элементов. Идея определения операции линеаризации с помощью вспомогательного списка, играющего роль стека, описана в [9].
domains elem{A} = at(A); list(elem{A}*). class predicates flatten: (elem{A}*) -> elem{A}*. flatten: (elem{A}*, elem{A}**) -> elem{A}*. clauses flatten(List) = flatten(List, []). flatten([list(L) | Tail], AuxList) = flatten(L, [Tail | AuxList]):- !. flatten([Head | Tail], AuxList) = [Head | flatten(Tail, AuxList)]. flatten([], [L | AuxList]) = flatten(L, AuxList). flatten([], []) = []. run():- L = [list([at(0)]), at(1), list([at(2), at(3), list([at(4), at(5), list([])])])], writef("%\n%", L, flatten(L)), _ = readLine().Пример 6.9. Линеаризация списка
В языке Visual Prolog имеются развитые средства обработки исключений. Например, если функция определена только на непустых списках, то ее можно "доопределить" так, что она станет всюду определенной:
class predicates first: (Elem*) -> Elem*. clauses first([H | _]) = [H]. first([]) = _:- exception::raise_error(predicate_name(), " Input list is empty.").
Если аргумент предиката first равен пустому списку, то возбуждается исключение. Предикат predicate_name возвращает имя предиката, в определении которого он участвует.
Упражнения
- Проверьте, является ли список палиндромом.
- Вычислите среднее арифметическое элементов списка, состоящего из целых чисел.
- Проверьте, каждый ли элемент первого списка, содержится во втором списке.
- Проверьте, является ли список упорядоченным по возрастанию или по убыванию.
- Найдите позицию первого вхождения подсписка в список.
- Вычислите все позиции заданного элемента в списке.
- Разбейте список на отрезки длиной по n элементов. Последний подсписок может содержать меньшее число элементов.
- Поменяйте местами два элемента списка, стоящих на заданных позициях.
- Вычислите номер заданного атомарного элемента в списке, содержащем вложенные списки (см. листинг 6.9).