 
        
        Л.Р._Ш.И_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.
Так как списки являются рекурсивными структурами, то для их обработки удобно использовать рекурсивные алгоритмы. Они получаются простыми и наглядными и состоят из двух ветвей:
- что делать с пустым списком?
- что делать со списком, у которого есть голова и хвост?
