Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методические указания по ИИС.doc
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
3.88 Mб
Скачать

Головы и хвосты

Список - действительно рекурсивный составной объект. Состоит из двух частей: голова списка, которая является первым элементом, и хвостовой части, которая является списком, включающим все последующие элементы. Хвост списка - последний элемент списка. Например,

Головой из списка [a, b, c] является a, хвостовой частью - [b, c], хвостом - c.

Если Вы берете первый элемент от хвостовой части списка много раз, Вы в конечном счете перейдете к пустому списку. Пустой список не может быть разбит на голову и хвостовую часть.

Это означает, что, концептуально, списки имеют древовидную структуру точно так же как другие составные объекты. Древовидная структура [a, b, c, d]:

list

/ \

a list

/ \

b list

/ \

c list

/ \

d []

Обработка списков

Пролог обеспечивает способ создавать голову и хвостовую часть явного списка. Вместо того, чтобы отделять элементы запятыми, Вы можете отделить голову и хвостовую часть вертикальной чертой (|). Например,

[a, b, c] is equivalent to [a|[b, c]]

и, продолжая процесс,

[a|[b, c]] = [a|[b|[c]]] = [a|[b|[c|[]]]].

Использование списков

Поскольку список - действительно рекурсивная составная структура данных, Вы нуждаетесь в рекурсивных алгоритмах, чтобы обработать его. Самый основной способ обрабатывать список состоит в том, чтобы работать с каждым элементом списка, пока Вы не достигаете его конца.

Алгоритм этого вида обычно нуждается в двух предложениях. Один из них говорит, что сделать с обычным списком (тот, который может быть разделен на голову и хвостовую часть). Другой говорит, что сделать с пустым списком.

Составные списки

Составные списки - списки, которые содержат больше чем один тип элемента. Вы нуждаетесь в специальных объявлениях, чтобы обработать списки элементов множественного типа, потому что Visual Prolog требует, чтобы все элементы в списке принадлежали тому же самому домену. Способ создавать список в Прологе, который сохраняет эти различные типы элементов, состоит в том, чтобы использовать функторы, потому что домен может содержать больше чем один тип данных как параметры к функторам.

Рассмотрим пример:

База данных содержит факты вида: student(имя, курс). Создать проект, позволяющий сформировать список студентов 1-го курса.

  1. Создайте новый проект:

PREDICATES

nondeterm student(symbol, integer)

spisok

CLAUSES

student(vova, 3).

student(lena, 1).

student(dima, 1).

student(ira, 2).

student(marina, 1).

spisok:- student(X, 1), write (X), nl, fail.

  1. Пропишите цель:

GOAL

write("Список студентов 1-курса"), nl, spisok.

Пролог должен вывести (рис. 1):

lena

dima

marina

Рис. 1. Вывод результата программы

  1. Создайте новый проект и сформируйте список из N элементов, начиная с 2. Каждый следующий на 4 больше предыдущего.

DOMAINS

list=integer*

PREDICATES

genl(integer, integer, list)

CLAUSES

genl(N2, N2, []):-!.

genl(N1, N2, [N1|L]):- N1<N2, N=N1+4, genl(N, N2, L).

GOAL

write(“N=”), readint(N), K=4*(N+1)-2, genl(2, K, L), write(L), nl.

Результат выполнения программы:

N=5

[2, 6, 10, 14, 18]

Список представляет собой последовательность, состоящую из любого количества элементов, таких как 2, 6, 10, 14, 18. Список можно рассматривать как состоящий из таких компонентов, как голова списка и хвостовая часть списка. Головой списка является - 2, а хвостовой частью - следующий список [6, 10, 14, 18] (рис. 2).

Рис. 2. Древовидное представление списка [2, 6, 10, 14, 18]

  1. Затем сформируем список последовательных натуральных чисел от 4 до 20 и найдём количество его элементов.

DOMAINS

list=integer*

PREDICATES

genl1(integer, integer, list)

len(integer, list)

CLAUSES

genl1(N2, N2, []):-!.

genl1(N1, N2, [N1|L]):- N1<N2, N=N1+1, genl1(N, N2, L).

len(0, []).

len(X, [_|L]):- len(X1, L), X=X1+1.

GOAL

genl1(4, 21, L), write(L), nl, len(X, L), write(“Количество элементов =”, X), nl.

Результат выполнения программы:

[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Количество элементов=17

  1. Теперь определим, содержится ли введенное число Х в заданном списке L.

Напишите следующий программный код:

DOMAINS

list=integer*

PREDICATES

member(integer, list)

CLAUSES

member(X, [X|_]):- write(“yes”), !.

member(X, []):- write(“no”), !.

member(X, [_|L]):- member(X, L).

GOAL

L=[1, 2, 3, 4], write(L), nl, write(“X=”, X), readint(X), member(X, L), nl.

Результат выполнения программы:

1-й случай:

[1, 2, 3, 4]

X=3

yes

2-й случай:

[1, 2, 3, 4]

X=5

no

  1. Сформируйте списки L1=[1, 2, 3], L2=[10, 11, 12, 13, 14, 15] и объединить их в список L3.

DOMAINS

list=integer*

PREDICATES

genl1(integer, integer, list)

append(list, list, list)

CLAUSES

genl1(N2, N2, []):-!.

genl1(N1, N2, [N1|L]):- N1<N2, N=N1+1, genl1(N, N2, L).

append([], L, L).

append([X|L1], L2, [X|L3]):- append(L1, L2, L3).

GOAL

genl1(1, 4, L1), write(“L1=”, L1), nl,

genl1(10, 16, L2), write(“L2=”, L2), nl,

append(L1, L2, L3), write(“L3=”, L3), nl.

Результат выполнения программы:

L1=[1, 2, 3]

L2=[10, 11, 12, 13, 14, 15]

L3=[1, 2, 3, 10, 11, 12, 13, 14, 15]

Подсчет элементов списка

Теперь рассмотрим пример, демонстрирующий, как можно узнать, сколько элементов находится в списке, т.е. какова его длина.

Длина [] - 0.

Длина любого другого списка - 1 плюс длина его хвостoвой части.

  1. Реализуйте программный код:

DOMAINS

list=integer*

PREDICATES

length_of(list, integer)

CLAUSES

length_of([], 0).

length_of([_|T], L):- length_of(T, TailLength), L=TailLength+1.

Второе предложение: кардинально, [_|T] будет соответствовать любому непустому списку, связывая T с хвостовой частью списка. Значение головы незначительно.

  1. Пропишите цель:

length_of([1, 2, 3], L).

Данная цель будет соответствовать второму предложению, с T = [2, 3]. Следующий шаг должен вычислить длину T. Когда это сделано, TailLength получит значение 2, и компьютер может тогда добавить 1 к этому и связать L с 3.

  1. Сравните результаты ответа (рис. 3):

Рис. 3. Вывод длины списка

Другими словами, length_of вызывается рекурсивно. Эта цель соответствует второму предложению.

Повторно, TailLength в цели не будет сталкиваться с TailLength в предложении, потому что каждое рекурсивное обращение предложения имеет его собственный набор переменных.

Теперь проблема состоит в том, чтобы найти длину [3], которая будет 1 и затем добавлять 1 к ней, который получить длину [2, 3], которая будет 2. Аналогично, length_of вызовет себя рекурсивно снова, чтобы получить длину [3]. Хвост [3] - [], так что T связан с [], и проблема состоит в том, чтобы получить длину [], затем добавлять 1 к этому, давая длину [3]. На сей раз это просто.

Цель

length_of([], TailLength)

соответствует первому предложению, связывая TailLength с 0. Так что теперь компьютер может добавить 1 к тому, что, дает длину [3], и возвращается к предложению запроса. Что, в свою очередь, добавит 1 снова, давая длину [2, 3], и возвратится к предложению, которое вызывало это. Оно добавит 1 снова, давая длину [1, 2, 3].

На следующей краткой иллюстрации мы будем суммировать запросы. Мы использовали приписки, чтобы указать, что переменные в различных предложениях или различных обращениях того же самого предложения являются отличными.

length_of([1, 2, 3], L1).

length_of([2, 3], L2).

length_of([3], L3).

length_of([], 0).

L3 = 0+1 = 1.

L2 = L3+1 = 2.

L1 = L2+1 = 3.

Изменение списка

Иногда необходимо из списка создать другой. Это осуществляется последовательной обработкой элементов списка, заменяя каждый вычисленной величиной.

  1. Следующий пример демонстрирует просмотр списка чисел и формирование нового, не учитывая отрицательные числа:

DOMAINS

list=integer*

PREDICATES

discard_negatives(list, list)

CLAUSES

discard_negatives([], []).

discard_negatives([H|T], ProsessedTail):- H<0, !, discard_negatives(T, ProsessedTail).

discard_negatives([H|T], [H|ProsessedTail]):- discard_negatives(T, ProsessedTail).

GOAL

discard_negatives([2, -45, 3, 468], X).

Пролог выдаст ответ (рис. 4):

X=[2, 3, 468].

Рис. 4. Изменнённый список

Членство в списке

  1. Предположим, что Вы имеете список с именами Джон, Леонард, Эрик, и Фрэнк и хотели бы использовать Visual Prolog для исследования, есть ли данное имя в этом списке. Другими словами, Вы должны выразить отношение «членство» между двумя параметрами: имя и список имен. Это соответствует предикату member(name, namelist).

  2. Опишите разделы доменов, предикатов, предложений и задайте цель:

DOMAINS

namelist=name*

name=symbol

PREDICATES

nondeterm member(name, namelist)

CLAUSES

member(Name, [Name|_]).

member(Name, [_|Tail]):-

member(Name, Tail).

GOAL

member(john, [john, leonard, eric, frank]).

В Программе, первое предложение исследует голову списка. Если голова списка равна имени, которое Вы ищете, то Вы можете заключить, что имя - член списка. Так как хвостовая часть списка неинтересна, она обозначена анонимной переменной. Благодаря этому первому предложению, цель member(john, [john, leonard, eric, frank]) удовлетворена.

  1. Пролог ответит yes (рис. 5).

Рис. 5. Подтверждение членства в списке

Если голова списка не равна Name, Вы должны исследовать, может ли Name быть найдено в хвостовой части списка.

Второе предложение member касается этих отношений. На Прологе:

member(Name, [_|Tail]) :- member(Name, Tail).

Рекурсия с процедурной точки зрения

  1. В следующем примере мы создадим предикат, чтобы добавить в список в окончание другого. Определим предикат с тремя параметрами: append(list1, list2, list3).

List1 и List2 используются, чтобы сформировать List3. Еще раз используем рекурсию.

Если List1 пуст, результат добавления в конец List1 и List2 будет тот же самый как List2. На Прологе:

append([], List2, List2).

Если List1 не пуст, Вы можете комбинировать List1 и List2, чтобы формировать List3, делая голову List1 головой List3. (В следующем коде, переменная H используется как голова и List1, и List3.) Хвостовая часть List3 третьего уровня, который составлен из остальной части List1 (а именно, L1) и всего List2. На Прологе:

append([H|L1], List2, [H|L3]):-

append(L1, List2, L3).

Предикат append работает следующим образом: В то время как List1 не пуст, рекурсивное правило передает один элемент одновременно List3. Когда List1 пуст, первое предложение гарантирует, что List2 связывается с задней частью List3.

  1. Загрузите программу:

DOMAINS

integerlist=integer*

PREDICATES

append(integerlist, integerlist, integerlist)

CLAUSES

append([], List, List).

append([H|L1], List2, [H|L3]):-

append(L1, List2, L3).

  1. Теперь выполните программу со следующей целью, Пролог должен вывести следующий ответ (рис. 6):

append([1, 2, 3], [5, 6], L).

Рис. 6. Вывод списка

  1. Задайте следующую цель (рис. 7):

append([1, 2], [3], L), append(L, L, LL).

Рис. 7. Вывод списков

Составные списки

Пример объявления домена для списка, который может содержать целое число, символ, строку, или список любого из них:

DOMAINS /* функторы l, i, c, и s */

llist = l(list); i(integer); c(char); s(string)

list = llist*

Список

[2, 9, ["food", "goo"], "new"] /* запись списка не на языке Пролог*/

записывается как

[i(2), i(9), l([s("food"), s("goo")]), s("new")] /* запись списка на языке Пролог*/

  1. Рассмотрим пример, который показывает, как использовать это объявление домена в типичной программе манипуляции списка.

DOMAINS

llist = l(list); i(integer); c(char); s(string)

list = llist*

PREDICATES

append(list, list, list)

CLAUSES

append([], L, L).

append([X|L1], L2, [X|L3]):-

append(L1, L2, L3).

GOAL

append([s(likes), l([s(bill), s(mary)])], [s(bill), s(sue)], Ans),

write(“FIRST LIST: ”, Ans, “\n\n”),

append([l([s(“This”), s(“is”), s(“a”), s(“list”)]), s(bee)], [c(‘c’)], Ans2),

write(“SECOND LIST: ”, Ans2, ‘\n’).

  1. Пролог выдаст ответ (рис. 8):

Рис. 8. Результат программы

Удаление элемента из списка

  1. Разберем задачу удаления из списка, элементами которого являются названия дней недели, указанного элемента.

  2. Создайте новый проект:

DOMAINS

list=symbol*

PREDICATES

del(symbol, list, list)

CLAUSES

del(X, [X|L], L).

del(X, [Y|L], [Y|L1]):- del(X, L, L1).

GOAL

L=[пн, вт, ср, чт, пт, сб, вс], write(“L=”, L), nl,

write(“X=”), readln(X),

del(X, L, L1), write(“L1=”, L1), !;

write(“Элемент отсутствует в списке”), nl.

Результат выполнения программы (рис. 9, 10):

1-й случай:

L=["пн","вт","ср","чт","пт","сб","вс"]

X=ср

L1=["пн","вт","чт","пт","сб","вс"]

Рис. 9. Удаление из списка дней недели среды

2-й случай

L=["пн","вт","ср","чт","пт","сб","вс"]

X=пр

Элемент отсутствует в списке

Рис. 10. Вывод результата программы при удалении несуществующего в списке элемента

  1. Вставим в список имен новый элемент, значение которого вводится с клавиатуры. Необходимо вывести все возможные варианты вставок.

  2. Создайте проект следующего содержания:

DOMAINS

list=symbol*

PREDICATES

del(symbol, list, list)

ins(symbol, list, list)

CLAUSES

del(X, [X|L], L).

del(X, [Y|L], [Y|L1]):- del(X, L, L1).

ins(X, L1, L):- del(X, L, L1).

GOAL

L=[olga, oksana, toma, dima], write(“L=”, L), nl,

write(“X=”), readln(X),

ins(X, L, L1), write(“L1=”, L1), nl, fail.

Результат выполнения программы:

L=["olga","oksana","toma","dima"]

X=vera

L1=["vera","olga","oksana","toma","dima"]

L1=["olga","vera","oksana","toma","dima"]

L1=["olga","oksana","vera","toma","dima"]

L1=["olga","oksana","toma","vera","dima"]

L1=["olga","oksana","toma","dima","vera"]