Л.Р._Ш.И_2-6 / L5_LIST
.docAIS_LZ_LIST
ЛАБОРАТОРНАЯ РАБОТА
Обработка списков на Прологе
Цель работы:
Разработка и отладка программ обработки списков. Приобретение и закрепление практических навыков составления программ на языке Prolog.
Подготовка к работе:
Изучить правила описания списков и типовые приемы обработки списков.
Порядок выполнения работы:
1. Загрузить компилятор языка логического программирования PROLOG.
2. Отладить программу печати списка.
domains
list = integer*
predicates
print(list)
clauses
print([]). если список пуст, то ничего не делать.
print(H | T):- write(H), напечатай значение головы
nl, перейди на следующую строку
print(T). напечатай хвост списка
Переставить два последних оператора программы. Что получиться?
3. Отладить программу вычисления длины списка двумя способами – общей и хвостовой рекурсией.
Определение длины списка (общая рекурсия)
длина(списка) = Длина(хвоста списка) + 1:
длина([]) = 0.
domains
list = integer*
predicates
len(list, integer)
clauses
len([]), 0). длина пустого списка
len([_|Tail], N):- len(Tail, N1), вычисление длины хвоста
N=N1+1. вычисление общей длины списка
Определение длины списка методом хвостовой рекурсии.
Программа получается более экономичной, если рекурсивный вызов является последним предикатом правила. Для преобразования общей рекурсии в хвостовую введем вспомогательный предикат, содержащий дополнительную переменную, в которой будет накапливаться длина списка при каждом рекурсивном вызове. Как только список станет пустым, в переменной окажется значение длины списка.
domains
list = integer*
predicates
len(list, integer)
lenv(list, integer, integer)
clauses
len(L, N):- lenv(L,N,0),!. третий аргумент – начальная длина списка равная нулю.
lenv([], N, N). когда список станет пустым, накопленное значение NT копируется в переменную N
lenv([_|Tail], N,NT):- NT1=NT+1,
lenv(Tail, N, NT1). рекурсивный вызов.
4. Составить и отладить программу (самостоятельно) вычисления суммы элементов списка методом общей и хвостовой рекурсии.
5. Отладить программу определения принадлежности элемента списку.
Введем предикат member(X, L). Рассмотрим два случая.
Первый случай – элемент X является первым элементом списка. Тогда выделив голову списка и сравнив ее с элементом X, при совпадении даем положительный ответ.
Второй случай - элемент X не является первым элементом списка. Тогда решаем задачу определения принадлежности элемента для хвоста списка. Текст программы приведен ниже.
domains
list = integer*
predicates
member(integer, list)
clauses
member(X, [X | _ ]).
member(X, [ _ | T ]) :- member(X, T).
Каков результат работы программы, если задать цель в виде:
member(X, [1, 2, 5]) и member(2, [1, 3, N]) ?
6. Отладить программу удаления элемента X из списка L.
Если Х окажется первым элементом списка, то задача решена. Ее решением является хвост списка. В противном случае надо удалить элемент Х из хвоста и присоединить к полученному списку голову.
domains
list = integer*
predicates
delete(integer, list, list)
clauses
delete(X, [X | T ], T).
delete(X, [ H | T ],[H | L]) :- delete(X, T ,L).
Какой смысл имеют цели:
delete(Z, [1, 2, 5], [1, 5]) delete(4, U, [1, 5])?
Если в списке содержится несколько одинаковых элементов, подлежащих удалению, то данный вариант программы удаляет только первый встретившийся элемент. Модифицируйте программу так, чтобы она удаляла все одинаковые элементы.
7. Отладить программу слияния и объединения списков.
Слияние списков merge(L1, L2, L)
domains
list = integer*
predicates
merge(list, list, list)
clauses
merge([], L2, L2).
merge([H | T], L2, [H | L3]) :- merge(T, L2, L3).
Если первый список пустой, то результат – это второй список. В противном случае необходимо слить хвост первого списка со вторым списком и добавить к полученному списку голову первого списка.
Объединение списков (в теоретико–множественном смысле). Основное отличие заключается в том, что элементы списка не должны повторяться, как это подобает множествам. Поэтому, если голова первого множества содержится во втором множестве, то ее не надо добавлять к полученному списку.
domains
list = integer*
predicates
union(list, list, list)
clauses
union([], L2, L2).
union([H | T], L2, L3) :-
member(H, L2), проверяем, входит ли голова H во второй список L2
union(T, L2, L3),!.
union([H | T], L2, [H | L3]) :-
union(T, L2, L3).
-
Индивидуальные задания. В таблице приведены 40 вариантов задач, разбитых на 6 групп. Необходимо решить по одной задаче из 5 выбранных групп. Номер варианта в каждой группе определяется по формуле: N= (ZZ mod G) +1, где ZZ – две последние цифры номера студенческой зачетки, G – количество вариантов в группе.
Содержание отчета
Отчет содержит название и цель работы, тексты программ и результаты тестирования.
Контрольные вопросы
1. Как компилятор распознает списки (константы и переменные)?
2. Каким знаком обозначается операция выделения головы списка, хвоста списка?
3. Как обозначается пустой список?
4. Из каких частей состоит любое рекурсивное правило?
5. Когда заканчивается рекурсивный вызов правил?
Варианты заданий
№ |
Балл |
Выполняемая операция |
Предикат |
|
|
1 группа Теоретико-множественные операции над списками |
|
1 |
18 |
Объединение двух упорядоченных по возрастанию списков |
union(L1, L2, L3) |
2 |
18 |
Объединение двух упорядоченных по убыванию списков |
union(L1, L2, L3) |
3 |
15 |
Пересечение двух неупорядоченных списков |
cross(L1, L2, L3) |
4 |
18 |
Пересечение двух упорядоченных по убыванию списков |
cross(L1, L2, L3) |
5 |
18 |
Пересечение двух упорядоченных по возрастанию списков |
cross(L1, L2, L3) |
6 |
15 |
Вычитание двух неупорядоченных списков |
diflist(L1, L2, L3) |
7 |
18 |
Вычитание двух упорядоченных по убыванию списков |
diflist(L1, L2, L3) |
8 |
18 |
Вычитание двух упорядоченных по возрастанию списков |
diflist(L1, L2, L3) |
|
|
2 группа Вставка элемента в список |
|
1 |
10 |
Вставка элемента X в упорядоченный по возрастанию список |
insert(X, L1, L2) |
2 |
10 |
Вставка элемента X в упорядоченный по убыванию список |
insert(X, L1, L2) |
3 |
10 |
Вставка элемента X на N-тое место в неупорядоченном списке |
insert(X, N, L1, L2) |
4 |
12 |
Вставка элемента X в список перед элементом Y списка |
insert(X, Y, L1, L2) |
5 |
12 |
Вставка элемента X в список после элемента Y списка |
insert(X, Y, L1, L2) |
6 |
10 |
Вставка элемента X в список вместо элемента Y списка |
insert(X, Y, L1, L2) |
7 |
5 |
Выделение N-того элемента из списка |
member(N, L, X) |
|
|
3 группа Удаление элементов из списка |
|
1 |
5 |
Удаление N-того элемента из списка |
del(N, L1, L2) |
2 |
12 |
Удаление последнего элемента списка |
del(L1, L2) |
3 |
15 |
Удалить из списка элементы с четными номерами |
del(L1, L2) |
4 |
15 |
Удалить из списка отрицательные элементы |
del(L1, L2) |
5 |
15 |
Удалить из списка четные (N mod 2 = 0) элементы |
del(L1, L2) |
|
|
4 группа Слияние и разделение списка |
|
1 |
10 |
Слияние двух упорядоченных по возрастанию списков |
append(L1, L2, L3) |
2 |
10 |
Слияние двух упорядоченных по убыванию списков |
append(L1, L2, L3) |
3 |
10 |
Разделить список на два списка, отличающихся по длине не более чем одним элементом |
Split(L1, L2, L3) |
4 |
12 |
Разделение списка L1 на два списка по компаратору К: L2 содержит числа меньше чем К, L3 - больше чем К |
Split(K, L1, L2, L3) |
5 |
15 |
Разделение списка L1 на два списка: L2 содержит четные числа, L3 - нечетные |
Split(L1, L2, L3) |
6 |
14 |
Разделение списка L1 на два списка. L2 содержит положительные числа, L3 - отрицательные |
Split(L1, L2, L3) |
7 |
18 |
Разделение списка L1 на два списка: L2 содержит числа меньше чем первый элемент списка, L3 - большие чем первый элемент списка |
Split(L1, L2, L3) |
|
|
5 группа Вычисление характеристик списка |
|
1 |
8 |
Вычисление количества четных N1 и нечетных чисел - N2 в списке L |
count(L, N1, N2) |
2 |
6 |
Вычисление количества положительных - N1 и отрицательных чисел - N2 в списке L |
count(L, N1, N2) |
3 |
8 |
Вычисление суммы четных – S1 и нечетных чисел - S2 в списке L |
sum(L, N1, N2) |
4 |
6 |
Вычисление суммы положительных – S1 и отрицательных чисел - S2 в списке L |
sum(L, N1, N2) |
5 |
5 |
Вычисление порядкового номера N элемента X в списке L |
number(X, L, N) |
6 |
12 |
Является ли список L1 подсписком списка L2 |
sublist(L1, L2) |
7 |
14 |
Нахождение максимального элемента списка методом деления списка пополам. В каждой половинке найти максимум и сравнить их. |
max(L, X_max) |
8 |
10 |
Нахождение максимального элемента списка путем нахождения его в хвосте и сравнения с головой списка |
max(L, X_max) |
|
|
6 группа (дополнительная) Сортировка списка |
|
1 |
15 |
Сортировка методом простого включения (отсортировать хвост списка и вставить голову в нужное место) |
sort(L1, L2) |
2 |
20 |
Сортировка методом простого выбора (найти максимальный элемент списка и формировать отсортированный список восходящей рекурсией) |
sort(L1, L2) |
3 |
20 |
Сортировка методом простого обмена (пузырька) |
sort(L1, L2) |
4 |
25 |
Быстрая сортировка списка (разделить список на два подсписка: элементы первого подсписка меньше, в второго – больше первого элемента исходного списка; подсписки отсортировать и соединить) |
sort(L1, L2) |
5 |
12 |
Инверсия списка (расположение элементов списка в обратном порядке) |
invers(L1, L2) |
Справочный материал
Список это множество элементов одного типа (одного домена). Списки принято записывать в виде последовательности элементов, разделенных знаком запятая и заключенных в квадратные скобки:
[1,3,5], [a,b,e,f], [jem,apple,jin]. Список может содержать произвольное количество элементов, и их количество может изменяться в процессе работы программы. Поэтому списки относятся к динамическим структурам. Частными случаями списка являются список, состоящий из одного элемента - [х] и пустой список - [].
Для списков базовыми операциями являются:
- выделение головы списка car([1,3,5])=1,
- выделение хвоста списка cdr([1,3,5])=[3,5],
- добавление элемента в голову списка cons(1, [3,5])=[1,3,5].
Эти три операции подчиняются следующей аксиоме:
cons(car(L), cdr(L))=L
В языке Prolog описание списка осуществляется в секции domains. Признаком списка является наличие символа * после описания типа элемента.
Примеры
domains
list=integer* список целых чисел [3,5]
l_char=char* список символов [a, f, h, d]
l_col=color* список красок [red, green, yellow]
color=symbol
Все три базовые операции со списками в языке Prolog обозначаются одним символом – вертикальная черта ( | ): L=[Head | Tail].
В зависимости от ситуации, программы сопоставления и унификации самостоятельно выполняют операции разделения списка на голову Head и хвост Tail или добавления элемента Head к списку Tail.
Так как списки являются рекурсивными структурами, то для их обработки удобно использовать рекурсивные алгоритмы. Они получаются простыми и наглядными и состоят из двух ветвей:
- что делать с пустым списком?
- что делать со списком, у которого есть голова и хвост?