Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование в среде Visual Prolog. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
903 Кб
Скачать

Министерство науки и высшего образования Российской Федерации

НОВОСИБИРСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ

Т. В. АВДЕЕНКО, М. Ю. ЦЕЛЕБРОВСКАЯ

ПРОГРАММИРОВАНИЕ В СРЕДЕ VISUAL PROLOG

Утверждено Редакционно-издательским советом университета в качестве учебного пособия

НОВОСИБИРСК

2022

1

УДК 004.8(075.8) А 187

Рецензенты:

канд. физ.-мат. наук, доцент, доцент кафедры ТПИ М. Г. Зайцев канд. техн. наук, доцент, доцент кафедры АСУ М. Ш. Муртазина

Авдеенко Т. В.

А 187 Программирование в среде Visual Prolog: учебное пособие / Т. В. Авдеенко, М. Ю. Целебровская. – Новосибирск: Изд-во НГТУ, 2022. – 46 с.

ISBN 978-5-7782-4689-8

Настоящее учебное пособие – это продолжение учебного пособия «Введение в искусственный интеллект и логическое программирование. Программирование в среде Visual Prolog», представляющего собой вводную часть курса по искусственному интеллекту, основной целью которой является изучение модели представления знаний на основе классических логических исчислений – исчисления высказываний и исчисления предикатов. В пособии рассматриваются: работа с рекурсивными структурами данных в Прологе, назначение и применение предиката «Отсечение», а также внелогические предикаты Пролога и предикаты работы со строками.

Предназначено длястудентовIII курсаФПМИ(направления подго-

товки 01.03.02, 02.03.03).

УДК 004.8(075.8)

ISBN 978-5-7782-4689-8

© Авдеенко Т. В., Целебровская М. Ю., 2022

 

© Новосибирский государственный

 

технический университет, 2022

2

Глава 5

РЕКУРСИВНЫЕ СТРУКТУРЫ ДАННЫХ

ВПРОЛОГЕ

Крекурсивным структурам данных в Прологе относятся списки и деревья.

5.1.СПИСКИ

Вфункциональных и логических языках списки используются чрезвычайно часто, они позволяют сохранить набор данных произвольной длины.

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

Связный список – структура данных, состоящая из узлов. Узел содержит данные и ссылку (указатель, связку) на один или два соседних узла. Списки языка Пролог являются односвязными, т. е. каждый узел содержит лишь одну ссылку.

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

илогических языках, они существенно отличаются от массивов, используемых в языках процедурного типа (таких как С++, Java, Pascal). В частности, элемент данных может быть очень быстро добавлен или

3

удален из начала односвязного списка. Однако операция произвольного доступа (обращения к n-ному элементу) в списках выполняется гораздо дольше, чем в массивах, поскольку требует n операций перехода по ссылкам.

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

На рис. 5.1 показан процесс обработки списка в Прологе.

Рис. 5.1

Рассмотрим синтаксисическую составляющую Пролога, ориентированную на работу со списками.

4

Структуры данных в логическом программировании записываются как функторы:

f (x1, x2, , xn ),

где f – имя функтора; xi – элементы данных для i от 1 до n.

Список – это бинарная рекурсивная структура данных. Первый компонент структуры – это элемент, второй – остаток списка. Таким образом, необходим функтор арности 2. В логическом программировании принято функтор для списков обозначать точкой « ». При построении списков необходимo наличие константного символа (чтобы рекурсия не была бесконечной). Таким символом является «пустой список», который в Прологе обозначается [].

Правильная формальная запись списка:(a, []) – список из одного элемента;

(а, (с, (в, []))) – список из трех элементов;(а, Х) – список, в котором выделен первый элемент;

(а, (b, Х)) – список с двумя выделенными первыми элементами. Чтобы не перегружать программу обращением к символу « », ис-

пользуется специальная запись: (Х, Y) обозначается как [X | Y].

Компоненты термаимеютспециальныеназвания: X – головасписка, Y – хвост списка.

Общепринятая запись перечисленных выше списков: [a| [ ] ] или [ а ];

[ а | [ b | [ c | [ ] ] ] ] или [a, b, c]; [ а | X ];

[ а | [ b | X] ] или[ а, b | X ].

Описание списков в Прологе

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

5

Например:

domains

list_integer = integer* % вводится домен списка целых чисел %

list_s =symbol* %

вводится домен списка символов %

list_list = list_s* %

элементамиэтогоспискадолжныбыть %

 

списки символов

Списки не обязательно должны быть построены из однородных элементов, можно объединить элементы разных типов. Для этого необходимо ввести домен с альтернативными типами. Это делается так:

domains

mixtype =I(integer); R(real);

S(symbol)

list_mix= mixtype* % это означает, что в списокмогутобъединяться % целые, вещественные, символьные элементы

Таким образом, общая схема описания списков:

domains

домен_список = домен_элементов* Далее рассмотрим примеры обработки списков. Наиболее часто

вПрологе необходимо выполнить следующие операции над списками:

определить принадлежность элемента списку;

объединить два списка;

выделить элемент из списка;

удалить все вхождения заданного элемента из списка;

отсортировать элементы списка;

определить длину списка.

Все эти операции могут быть реализованы в Прологе только с использованием рекурсии. Именно поэтому мы и относим списки в Прологе к рекурсивным структурам данных.

Рассмотрим реализацию некоторых операций.

6

1. Определение принадлежности элемента списку

Разработаем предикат, позволяющий проверить принадлежность элемента списку. Предикат будет иметь два аргумента: первый – искомое значение, второй – список, в котором производится поиск.

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

member(X,[X|_]). /* X — первый элемент списка */ member(X,[_|T]) :

member(X,T). /* X принадлежит хвосту T*/

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

Описанный предикатможно использоватьдлярешениясразу трех задач: во-первых, для того, для чего мы его и создавали, т. е. для проверки: имеется ли в списке конкретное значение. Мы можем, например, поинтересоваться, принадлежит ли двойка списку [1, 2, 3] (первый способ):

member(2, [1, 2, 3]).

Получим, естественно, ответ Yes.

Подобным образом можно спросить, является ли число 4 элементом списка [1, 2, 3]:

member(4, [1, 2, 3]).

Ответом, конечно, будет No.

Второйспособиспользованияданногопредиката– этополучениепо списку его элементов. Для этого нужно в качестве первого аргумента предиката указать свободную переменную. Например:

member(X, [1, 2, 3]).

В качестве результата получим список всех элементов списка:

X=1

X=2

X=3

7

Третий способ позволит получить по элементу варианты списков, которые могут его содержать. Теперь свободную переменную запишем вторымаргументомпредиката, апервым– конкретноезначение. Например,

member(1, X).

В начале работы программы Пролог-система выдаст предупреждение о том, что переменная X является несвязанной в первом предложе-

нии ("708 WARNING): The variable is not bound in this clause. (F10=ok, Esc=abort)"). У нас есть два способа отреагировать на это предупреждение: нажать кнопку Esc, чтобы отказаться от генерации списков, содержащих единицу в качестве элемента; нажать F10 для того, чтобы продолжить выполнение цели. Во втором случае Пролог-система начнет выдавать варианты списков, содержащих единицу:

X=[1|_] /* единица – первый элемент списка */

X=[_,1|_] /* единица – второй элемент списка */

X=[_,_,1|_] /* единица – третий элемент списка */ и т. д.

Этот процесс будет продолжаться до тех пор, пока не будет нажата комбинация клавиш Ctrl+Break.

2. Объединение списков

Создадим предикат, позволяющий соединить два списка в один. Первые два аргумента предиката будут представлять соединяемые списки, а третий – результат объединения. Идея алгоритма будет состоять в последовательном отделении голов от первого списка до тех пор, пока не получим пустого списка, и обратной «сборке» – присоединении по порядку ко второму списку последних отделенных голов первого списка. Базисом рекурсии будет факт, устанавливающий, что если присоединить к списку пустой список, то получим исходный список. Шагом рекурсии будет служить правило, определяющее, что для того чтобы приписать элементы списка, состоящего из головы и хвоста, ко второму списку, нужно соединить хвост и второй список, а затем к результату приписать спереди первый элемент первого списка. Запишем решение:

8

conc([ ], L, L). /* при присоединении пустого списка к списку L получим список L */

conc([H|T], L, [H|T1]) :–

conc(T,L,T1). /* голова объединенного списка совпадает с головой первого списка, а хвост есть результат объединения хвоста первого списка со вторым */

Этот предикат также можно применять для решения нескольких задач. Во-первых, для соединения списков. Например, если задать вопрос

conc([1, 2, 3], [4, 5], X).

то получим в результате

X= [1, 2, 3, 4, 5]

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

conc([1, 2, 3], [4, 5], [1, 2, 5]).

ответом будет, конечно, No.

В-третьих, можно использовать этот предикат для разбиения списка на подсписки. Например, если задать следующий вопрос:

conc([1, 2], Y, [1, 2, 3]).

то ответом будет Y=[3]. Аналогично, на вопрос

conc(X, [3], [1, 2, 3]).

получим ответ X=[1, 2].

И, наконец, можно спросить conc(X, Y, [1, 2, 3]).

Получим четыре решения:

X=[], Y=[1, 2, 3]

X=[1], Y=[2, 3]

X=[1, 2], Y=[3]

X=[1, 2, 3], Y=[]

В-четвертых, можно использовать этот предикат для поиска элементов, находящихся левее и правее заданного элемента. Например, если

9

нас интересует, какие элементы находятся левее и соответственно правее числа 2, можно задать следующий вопрос:

conc(L, [2|R], [1, 2, 3, 2, 4]).

Получим два решения:

L=[1], R=[3, 2, 4].

L=[1, 2, 3], R=[4]

В-пятых, на основе предиката conc можно создатьпредикат, находящий последний элемент списка:

last(L,X):– conc(_,[X],L).

Конечно, этот предикат можно было бы написать и без использования предиката conc:

last2([X],X). /* единственный элемент списка является последним */ last2([_|L],X):–

last2(L,X). /* последний элемент любого списка совпадает с последним элементом хвоста этого списка */

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

member4(X,L):– conc(_,[X|_],L).

В-седьмых, используя предикат, позволяющий объединить списки, можносоздатьпредикат, проверяющийподвумзначениямисписку, являются ли эти значения соседними элементами списка. Предикат будет иметь три параметра: первые два – значения, третий – список.

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

10

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]