
- •Введение
- •Принципы логического программирования
- •1.2. Математическая основа языка Пролог
- •1.2. Организация вычислительного процесса
- •1.2.2. Использование переменных
- •1.2.1. Синтаксис фактов и правил
- •1.3. Бэктрекинг
- •2. Основные элементы языка
- •2.1. Имена
- •2.2. Типы данных
- •2.3. Константы и переменные
- •2.4. Программные секции Пролога
- •2.4.1. Секция Domains
- •2.4.2. Секция Predicates
- •2.4.3. Секция Database
- •2.4.4. Секция Clauses
- •2.4.5. Секция Goal
- •3. Язык Пролог в задачах и примерах
- •3.1. Программирование с помощью фактов и правил
- •3.1.2. Первая формулировка задачи поиска в пространстве состояний 51 ад
- •3.1.3. Реализация на Прологе простой вопросно-ответной системы
- •It_is(“отказать в приеме на работу”):- not(this(“есть диплом”)).
- •It_is(“должность научного сотрудника”):- this(“есть диплом”),
- •3.2. Рекурсии
- •Упражнения
- •3.3. Программирование циклических процессов
- •3.4. Работа со списками
- •3.4.1. Описание списков в программе
- •3.4.2. Добавление элемента в список
- •3.4.3. Удаление элемента
- •3.4.4. Принадлежность элемента списку
- •3.4.5. Сцепление (конкатенация) списков
- •3.4.6. Удаление из списка повторяющихся элементов
- •3.4.7. Вычисление суммы элементов списка
- •3.4.8. Обращение списка
- •3.4.9. Нахождение максимального элемента списка
- •3.4.10. Перестановки
- •3.4.11. Примеры использования списков
- •Упражнения
- •3.5. Виды рекурсии
- •3.6. Поиск в пространстве состояний
- •Vshir ( [ [ V | Way ] | _ ], [ V | Way ] ) :- % Голова списка – полученное решение
- •Vshir ( Ways, Resh ). % Продолжение поиска в случае тупикового пути
- •Упражнения
- •3.6. Использование структур
- •3.6.1. Объявление структур
- •База данных с использованием структур.
- •Vife(X) :– family( _ , X , _ ). % X – жена
- •3.6.4. Планирование воздушного путешествия (143 Бр)
- •3.6.5. Реализация Планировщика в терминах структур
- •3.6.6. Задача «Зебра»
- •Упражнения
- •3.7. Динамическая база данных
- •3.7.1. Использование стандартных предикатов динамической базы данных
- •Упражнения
- •3.8. Средства управления
- •3.9. Представление множеств двоичными деревьями
- •3.9. Программы классификации
- •3.9.1. Программа классификации с обратной цепочкой рассуждений
- •Xpositive( X, y ), !. % в базе данных
- •Xnegative( X, y ), !, fail. % Отрицательный ответ обнаружен в базе данных
- •Xpositive("имеет","перья").
- •3.9.2. Программы классификации с прямой цепочкой рассуждений.
- •It_is( X ) :- write( X, “?”), % Механизм диалога
- •3.9. Обработка текстов
- •Verb( string ) % Глагол
- •Упражнения
- •4. Стандартные предикаты
- •4.1. Ввод/вывод
- •4.2. Управление экраном и оконная система
- •4.3. Обработка строк
- •4.4. Преобразование типов
- •4.5. Работа с базой данных
- •4.6. Управляющие предикаты
- •4.7. Прочие стандартные предикаты
- •4.8. Арифметические и логические предикаты
- •Приложение Приложение 1. Примерные варианты лабораторных заданий
- •1. Родословное дерево
- •2. Вопросно-ответная система
- •3. Работа со списками
- •4. Поиск пути на графе.
- •5. Разработайте прототип классификационной экспертной системы
- •6. Построение синтаксического анализатора
- •Рекомендуемая литература
3.4.8. Обращение списка
Определим предикат
reverse( L1, L2 ).
Аргументы L1 и L2 – два списка, из которых список L2 содержит элементы списка L1, записанные в обратном порядке.
reverse( L1, L2 ):– reverse1( L1, [ ], L2 ).
reverse1( [ ], L, L ).
reverse1( [ H | T ], L1, L2 ):– reverse1( T, [ H | L1 ], L2 ).
Предикат reverse в данном случае является интерфейсным, он запускает в работу основной рабочий предикат reverse1, имеющий дополнительный второй аргумент – список, который вначале пуст, и используется собственно для обращения списка. На каждом шаге рекурсии один элемент исходного списка становится головой промежуточного списка. Третий аргумент передается от шага к шагу и конкретизируется в момент достижения базового состояния предиката reverse1. Когда первый список исчерпан, второй уже содержит элементы, записанные в обратном порядке. Отметим, что наличие третьего аргумента, фиксирующего результат, обязательно, т.к. после обратного прохождения рекурсии все конкретизированные переменные принимают свои первоначальные значения. Проследить работу этого предиката можно, если его расписать, как в предыдущем примере.
3.4.9. Нахождение максимального элемента списка
Здесь нам понадобится вспомогательный предикат max, выбирающий максимальное значение из двух элементов:
max( X, Y, X ):– X >= Y.
max( X, Y, Y ):– X < Y.
Результативный предикат maxlist(LIST, MAX), где MAX – наибольший элемент списка LIST, выглядит следующим образом:
maxlist( [ X ], X ).
maxlist( [ X, Y | TAIL ], MAX ) :–
maxlist( [ Y | TAIL ], MAXTAIL ),
max( X, MAXTAIL, MAX ).
Смысл базового правила: максимальный элемент одноэлементного списка равен самому этому элементу. Иначе, если в списке есть хотя бы два элемента X и Y, выбирается максимальный элемент хвоста MAXTAIL и при этом MAX равен наибольшему из X и MAXTAIL. Рекурсия здесь нехвостовая, чтобы обеспечить конкретизацию переменных X и MAXTAIL.
3.4.10. Перестановки
В различных логических задачах часто требуется строить перестановки элементов некоторого заданного списка. Для этого определим отношение permutation двумя аргументами. Аргументы – это два списка, один из которых является перестановкой другого.
Программирование отношения permutation основывается на рассмотрении двух случаев:
(1) Если первый список пуст, то и второй список должен быть пустым.
(2) Если первый список не пуст, тогда он имеет вид [ X | L ], и перестановку такого списка можно построить так: вначале получить перестановку списка L1, а затем внести X в произвольную позицию L1.
Два прологовских предложения, соответствующие этим случаям, таковы:
permutation ( [ ], [ ] ).
permutation ( [ X | L ], P ) :-
permutation ( L, L1 ),
add2( X, L1, P ).
Программирование отношения permutation можно реализовать и другим способом, если вспомнить, что отношение «внести» программируется через «удалить».
3.4.11. Примеры использования списков
Программирование рекурсивных предикатов работы со списками как основными структурами данных представляет собой увлекательное занятие.
Пролог идеально подходит для решения различного рода математических ребусов и головоломок, требующих перебора. При этом пространство состояний удобно представлять в виде списков, из которого можно «черпать» элементы. Рассмотрим одну такую головоломку.
Т
ребуется
расставить в кружочках числа от 1 до 25
таким образом, чтобы каждая сумма по
каждой диагонали была равна 25 и сумма
вершин треугольника тоже бы равнялась
25. Разным буквам должны соответствовать
разные числа, иначе возможно тривиальное
решение.
Список чисел, из которого будут конкретизироваться переменные, определим как факт cifr. Введем отношение summa, аргументами которого будут семь переменных, которые предстоит конкретизировать с выполнением заданных условий. В программе действия по конкретизации переменных выполняет предикат удаления элемента из списка away. Это трехаргументное отношение, в котором в нашем примере конкретизирован второй аргумент – список доступных чисел. Предикат конкретизирует число из этого списка и возвращает третий аргумент – список без удаленного числа. Путем последовательных удалений будут конкретизированы все числа.
Полная программа для решения головоломки приводится ниже.
Domains
i = integer
ILIST= i *
predicates
summa( i, i, i, i, i, i, i, ILST )
away( i, ILIST, ILIST )
cifr( ILIST )
clauses
cifr([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]). % Столько чисел хватит
summa( A, B, C, D, E, F, G, CIF ) :-
away( A, CIF, CIF1 ),
away( B, CIF1, CIF2 ),
away( C, CIF2, CIF3 ),
away( D, CIF3, CIF4 ),
away( E, CIF4, CIF5 ),
away( F, CIF5, CIF6 ),
away( G, CIF6, _ ),
A + G + D = 25,
B + G + E = 25,
C + G + F = 25,
A + C + E = 25.
away( A, [ A | L ], L ).
away( A, [ B | L ], [ B | L1 ] ):-
away( A, L, L1 ).
goal cifr( CIF ), % Переменная CIF конкретизируется списком чисел
summa( A, B, C, D, E, F, G, CIF ), write( A, B, C, D, E, F, G ).
В данном случае у нас найдено только одно решение. Для получения всех решений следует включить бэктрекинг (см. «Организация циклов»).