Списки. Полиморфизм
Упражнение 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).