- •Введение
- •1. Организация вычислительного процесса
- •2. Основные элементы языка
- •2.1. Имена
- •2.2. Типы данных
- •2.3. Константы и переменные
- •2.4. Программные секции Пролога
- •2.4.1. Секция Domains
- •2.4.2. Секция Ppredicates
- •2.4.3. Секция Database
- •2.4.4. Секция Clauses
- •2.4.5. Секция Goal
- •3. Примеры программ
- •3.1. Программы с фактами и простыми правилами
- •3.2. Рекурсии
- •3.3. Программирование циклов
- •3.4. Работа со списками
- •3.5. Нахождение пути на графе
- •3.6. Использование структур
- •Vife(X):–family(_,X,_). % X – жена
- •3.7. Динамическая база данных
- •It_is("хищник"),
- •Xpositive(X, y), !. % в базе данных
- •3.8. Обработка текстов
- •Verb( string ) % Глагол
- •4. Стандартные предикаты
- •4.1. Ввод/вывод
- •4.2. Управление экраном и оконная система
- •4.3. Обработка строк
- •4.4. Преобразование типов
- •4.5. Работа с базой данных
- •4.6. Управляющие предикаты
- •4.7. Прочие стандартные предикаты
- •4.8. Арифметические и логические предикаты
- •5. Использование языка Пролог для построения экспертных систем
- •5.1. Оболочка экспертной системыGeni
- •5.2. Оболочка экспертной системы pexpert
- •5.2.1. Синтаксис правил
- •5.2.2. Функции
- •5.2.3. Взгляд на работу программы
- •5.2.4. Команды верхнего уровня
- •5.2.5. Команды оценки правил
- •5.2.6. Команды, действующие во время ввода данных
- •Рекомендуемая литература
- •Приложение. Варианты лабораторных работ Лабораторная работа 1. Работа с простой базой данных
- •Лабораторная работа 2. Программа “Родственные отношения”
- •Лабораторная работа 3. Построение простой вопросно-ответной системы
- •Лабораторная работа 4. Работа со списками
- •Лабораторная работа 5. Нахождение пути на графе
- •Лабораторная работа 6. Работа с базой данных с использованием структур
- •Лабораторная работа 7. Построение экспертной системы
- •Лабораторная работа 8. Построение синтаксического анализатора
3.5. Нахождение пути на графе
19. Поиск пути на графе – классическая задача. На Прологе она записывается очень компактно. После несложного расширения эта программа сможет отвечать на следующие вопросы:
- как добраться из одной вершины в другую, не посещая дважды одной и той же вершины;
- как пройти по маршруту, посетив заданную вершину;
- как добраться до нужной вершины, избежав некоторых вершин.
Центральной частью программы будет база данных, содержащая данные о сети дуг между вершинами. Эта информация представлена в виде двухаргументного отношения road, например:
road(v1,v3).
road(v4,v1).
road(v3,v5).
Необходимо иметь информацию об обратных направлениях. Чтобы не дублировать базу данных, воспользуемся вспомогательным предикатом road1:
road1(T1,T2):–road(T1,T2);
road(T2,T1).
Для решения этой задачи удобнее всего собирать в список все пройденные вершины пути, исходя из следующих соображений: если существует дуга из вершины X в вершину Y, то путь найден (базовое правило), иначе чтобы найти путь из X в Y, надо найти дугу из X в Z и затем путь из Z в Y (рекурсивное правило):
way(X,Y,[ ]):–road1(X,Y).
way(X,Y,[Z|W]):–road1(X,Z), way(Z,Y,W).
Очевиден недостаток такой записи: система не смущаясь будет находить маршруты, многократно проходящие через одну и ту же вершину. Чтобы избежать этого, следует добавить проверку непринадлежности очередной вершины найденному списку вершин:
way(X,Y,[Z|W]]);- road1(X,Z), way(Z,Y,W), not(membr(Z,W)).
Здесь member– знакомый предикат проверки принадлежности элемента списку.
Рекурсия неизбежно становится нехвостовой, поскольку для предиката memberнеобходимо иметь конкретизированную величину списка W (а список этот конкретизируется только при обратном прохождении рекурсии). Но в такой рекурсии есть опасность зацикливания: можно без конца совершать переходы из одной вершины в другую и обратно, т.к. до проверки непринадлежности вершины маршруту вычисления просто не доходят. Очевидна необходимость хвостовой рекурсии. Можно перейти к такой рекурсии, если начать вычисления от конкретизированного списка (например, пустого) и иметь уже тем больший список, чем больше вложенность рекурсии, т.е. накапливать список в правой части правила. Но в таком случае накопленный список надо зафиксировать в базовом правиле введением дополнительного аргумента:
way(X,Y,W,W):–road1(X,Y).
way(X,Y,W,L):– road1(X,Z), not(member(Z,W)),
way(Z,Y,[Z|W],L).
Для запуска этой программы можно воспользоваться интерфейсным предикатом go, аргументы которого содержат начальный и конечный пункты:
go(Here,There):– way(Here,There,[Here],W),
add(There,W,W1), reverse(W1,W2), write(W2).
Здесь исходный список маршрута конкретизируется начальной вершиной. К списку найденного маршрута добавляется конечный пункт (предикат add). Предикатreverseвыполняет инверсию этого списка. Клозы этих последних предикатов было записано ранее.
3.6. Использование структур
Структуры данных являются мощным средством программирования в Прологе. Они позволяют организовать различные фрагменты информации в единые логические единицы, что дает возможность использовать информацию, не думая о деталях ее действительного представления. Широкое применение структур – это также средство представления структурных объектов (баз данных) и управления ими. Использование структур делает Пролог и естественным языком запросов к базе данных.
Структура данных в Прологе задается на составной области определения (третий способ указания Domains).
Рассмотрим ряд примеров с использованием структур.
20. База данных "Семья". Каждая семья состоит, с точки зрения логики, из трех объектов: мужа, жены и детей. Поскольку количество детей в разных семьях может быть разным, то их целесообразно представить в виде списка, состоящего из произвольного числа элементов. Каждого члена семьи в свою очередь можно представить структурой, состоящей из четырех компонент: имени, фамилии, даты рождения и работы. Информация о работе – это либо "не работает", либо указания кода работы и оклада.
Ясно, что для описания такой базы данных понадобится сложная область определения. Введем следующие структуры, соответствующие соответственно дате рождения члена семьи (число, месяц, год) и работе :
Domains
data=dat(integer,string,integer)
work=worker(string,integer);notwork
Тогда основная структура, соответствующая описанию каждого члена семьи, может выглядеть таким образом:
memfam=mem(string,string,data,work)
Информацию о семьях можно занести в базу данных с помощью следующих предложений:
family(mem(jon,kelly,dat(17,may,1950),worker(bbz,15000)),
mem(bony,kelly,dat(29,may,1951),notwork)),
[mem(pat,kelly,dat(5,april,1983),notwork),
mem(liz,kelly,dat(10,april,1960),notwork)).
family(mem(bob,rob,dat(14,may,1930),worker(pla,12000)),
mem(sally,rob,dat(5,october,1931),worker(plt,11000)),[ ]). % нет детей
Теперь из такой базы данных уже можно извлекать информацию. Так, в запросах к базе данных можно ссылаться на всех kellyс помощью терма:
goal family(mem(_,kelly,_,_),_,_).
Символы подчеркивания обозначают различные анонимные переменные, значения которых нас не интересуют.
Можно найти все семьи с тремя детьми при помощи выражения:
goal family(X,_, [_,_,_]).
Чтобы найти имена и фамилии всех женщин, имеющий по крайней мере трех детей, можно задать вопрос:
goal family(_, mem(Name,Fam,_,_),[_,_,_|_]).
Главным моментом в этих примерах является то, что указывать интересующие нас объекты можно не только по их содержимому, но и по их структуре, т.е. иметь результат в виде целой структуры или в виде отдельных элементов структур.
Можно также создать набор процедур, который делал бы взаимодействие с базой данных более удобным. Такие процедуры являлись бы частью пользовательского интерфейса. Вот некоторые из них:
husband(X):–family(X,_,_). % X – ìóæ