- •Лабораторная работа № 1. Малая экспертная система.
- •Теоретические сведения
- •Диагностические экспертные системы.
- •Пример применения байесовской стратегии оценки выводов
- •Контрольные задания:
- •Контрольные вопросы:
- •Лабораторная работа № 2. Знакомство с инструментальными средствами для создания экспертных систем.
- •Теоретические сведения
- •Контрольное задание:
- •Контрольное задание:
- •Контрольные вопросы:
- •Лабораторная работа № 4.
- •Теоретические сведения
- •Контрольное задание:
- •Основные стандартные домены
- •Основные стандартные предикаты:
- •Ключевые слова
- •Контрольные задания:
- •Контрольные вопросы:
- •Лабораторная работа №7. Предложения (факты и правила), цели на языке пролог.
- •Теоретические сведения Clauses (условия): Facts (факты) и Rules (правила)
- •О фактах
- •Контрольные вопросы:
- •Лабораторная работа №8. Переменные на языке программирования пролог.
- •Теоретические сведения
- •Анонимные переменные
- •Контрольное задание:
- •Принципы отката:
- •Контрольные задания:
- •Преимущества рекурсии
- •Оптимизация обратной рекурсии
- •Контрольные задания:
- •Объявление списков
- •Головы и хвосты
- •Обработка списков
- •Использование списков
- •Контрольное задание:
- •Контрольные вопросы:
- •Лабораторная работа №13. Секция фактов Лабораторная работа № 1.
- •Теоретические сведения
- •Объявление секций фактов
- •Модификация секции фактов
- •Добавление фактов в период исполнения программы
- •Загрузка фактов из файла в период исполнения программы
- •Удаление фактов в период исполнения программы
- •Удаление нескольких фактов сразу
- •Ключевые слова для объявления фактов
- •Описания Факты, объявленные с ключевым словом nondeterm
- •Факты, объявленные с ключевым словом determ
- •Факты, объявленные с ключевым словом single
- •Сохранение базы данных фактов во время выполнения программы
- •Контрольные задания:
- •Контрольные вопросы:
Преимущества рекурсии
Рекурсия имеет три главных преимущества:
может выразить алгоритмы, которые не могут удобно быть выражены никаким другим путем;
логически более проста, чем повторение;
используется экстенсивно в обработке списка.
Рекурсия - естественный способ описать любую проблему, которая содержит в пределах себя другую проблему того же самого вида.
Оптимизация обратной рекурсии
Рекурсия имеет один большой недостаток: всякий раз, когда одна процедура вызывает другую, состояние процедуры запроса выполнения должно быть сохранено так, чтобы процедура запроса могла возобновиться, где она кончилась, когда вызванная процедура закончилась. Это означает, что, если процедура вызывает себя 100 раз, 100 различных состояний выполнения должны быть сохранены одновременно. Как оказывается, этот недостаток не ограничивает мощь языка Пролог. Фактически, Пролог признает специальный случай рекурсии, называемый рекурсией хвоста, и компилирует это в повторяющийся цикл на языке машины. Это означает, что, хотя логика программы выражена рекурсивно, компилируемый код столь же эффективен, как если это было бы в Паскале или Бейсике. Рекурсия в большинстве случаев более ясна, логична и менее подвержена ошибкам, чем циклы, которые используют обычные языки.
Оказывается, что есть специальный случай, в котором процедура может вызвать себя, не храня состояние выполнения. Что, если процедура запроса не собирается возобновлять после вызванных концов процедуры?
Предположим, что процедура запроса вызывает процедуру самым последним шагом. Это означает, что процедура запроса не должна спасать свое состояние выполнения, потому что та информация больше не необходима. Как только окончатся вызванные процедуры, управление может идти непосредственно туда, куда оно ушло бы, когда процедура запроса закончилась.
Например, процедура вызвала B, и B вызывает процедуру C как ее самый последний шаг. Когда B вызывает C, B не собирается делать что-нибудь еще. Так, вместо того, чтобы хранить текущее состояние выполнения для C под B, можно заменить старое сохраненное состояние B, которое не необходимо больше, текущим состоянием C, делая соответствующие изменения в сохраненной информации. Когда кончится C, она думает, что ее вызывали непосредственно.
Теперь предположим что вместо того, чтобы вызвать C, процедура B вызывает себя как самый последний шаг. Когда B вызывает B, структура стека для запроса B должна быть заменена структурой стека для вызванного B. Это особенно простое действие; только аргументы должны быть установлены на новые величины. Так, с процедурной точки зрения, то, что случается, очень похоже на обновление переменных контроля в цикле.
Это называют оптимизацией обратной рекурсии или оптимизацией последнего запроса. Обратите внимание на то, что по техническим причинам, рекурсивные функции (предикаты, возвращающие величины) не могут быть так оптимизированы.
Давайте рассмотрим следующие примеры рекурсии.
Пример 1.
Имеется база данных, которая содержит следующие факты:
roditel(ivan,oleg). % иван является родителем олега
roditel(inna,oleg). % инна является родителем олега
roditel(oleg,dima). % олег является родителем димы
roditel(oleg,marina). % олег является родителем марины
Составить рекурсивное правило предок и определить всех предков и их потомков.
Решение:
Описание раздела доменов. Дадим имя аргументу - name и определим его тип.
name=string
Назовем предикаты: roditel и predok, которые будут иметь по два аргумента - name.
nondeterm roditel(name, name)
nondeterm predok(name, name)
Теперь составим правила. Обозначим аргументы предикатов (roditel, predok) как Х, У, Z.
Исходя из данной нам базы данных, составим правила таким образом, чтобы предикаты меняли свои объекты местами:
predok(X, Z):- roditel(X, Z).
predok(X, Z):- roditel(X, Y), predok(Y, Z).
Для того, чтобы Пролог вывел результат: всех предков и его потомков, опишем цель predok(X, Y). Стандартный предикат write - выводит аргументы, заключенные в кавычки. Предикат fail - предикат возврата.
В общем виде программный код данного примера выглядит так:
DOMAINS
name=string
PREDICATES
nondeterm roditel(name, name)
nondeterm predok(name, name)
CLAUSES
roditel(ivan,oleg).
roditel(inna,oleg).
roditel(oleg,dima).
roditel(oleg,marina).
predok(X, Z):- roditel(X, Z). % нерекурсивная часть правила
predok(X, Z):- roditel(X, Y), predok(Y, Z). % рекурсивная часть правила
GOAL
predok(X, Y), write(“Predok - ”, X, “Ego potomok - ”, Y), nl, fail.
Результат выполнения программы (рис. 1):
Predok - ivan Еgo potomok - oleg
Predok - inna Еgo potomok - oleg
Рredok - oleg Еgo potomok - dima
Рredok - oleg Еgo potomok - marina
Рredok - ivan Еgo potomok - dima
Рredok - ivan Еgo potomok - marina
Рredok - inna Еgo potomok - dima
Рredok - inna Еgo potomok - marina
Р
ис.
1. Вывод результата программы
Рассмотрим следующий пример вычисления факториала.
Пример 2.
Правило для вычисления факториала можно сформулировать следующим образом: если число - 0, то его факториал равен 1, в противном случае факториал числа N - это произведение (N-1)! на N.
Решение:
PREDICATES
nondeterm start
nondeterm fact(integer, integer)
CLAUSES
start:- write(“Vvedite chislo: ”), readint(N), fact(N, Nf), write(“Factorial: ”, Nf).
fact(0, 1):- !. % факториал нуля равен единице
fact(N, Nf):- N1=N-1, % уменьшаем N на единицу
fact(N1, N1f), % вычисляем факториал нового числа
Nf=N1f*N. % а затем умножает его на N
GOAL
start.
Рассмотрим работу программы при вычислении 2!. При вызове предиката fact сначала рассматривается его первый клоз. Однако попытка сопоставления предикатов fact(N, Nf) и fact(0, 1) заканчивается неудачей из-за несовпадения первого аргумента, так как N=2. Рассматривается второй клоз предиката fact. Вычисляется переменная N1=2-1=1, и вызывается предикат fact(N1, N1f), где N1=1 (переменная N1f пока свободна). Таким образом, выполняется рекурсия (предикат fact вызывает сам себя). Рассматривается первый клоз предиката fact, т.е. делается попытка сопоставления предикатов fact(N1, N1f) и fact(0, 1). Сопоставление заканчивается неудачей, так как N1=1. Рассматривается второй клоз предиката fact: происходит успешное сопоставление предикатов fact(N1, N1f) и fact(N, Nf). В результате этого переменная N получает значение 1. Выполняется также унификация переменных N1f и Nf (пока они обе свободны, но когда одна из них получит значение, другой будет присвоено тоже значение). Важно понимать, что при этом в памяти сохраняется информация о том, что обработка предыдущего вызова предиката fact (с аргументом N=2) еще не закончена. Вычисляется переменная N1=N-1=1-1=0, и вызывается предикат fact(N1, N1f). Для его доказательства рассматривается первый fact(0, 1). Сопоставление предикатов fact(N1, N1f) и fact(0, 1) выполняется успешно, так как N1=0, а N1f - свободная переменная. В результате унификации переменная N1f получает значение 1. Так как доказан предикат fact(N1, N1f) (где N1=0, а N1f получила значение 1), доказывается следующий предикат: Nf=N1f*N, где N1f=1 и N=1. Переменная Nf получает значение 1.
Таким образом, доказан предикат fact(N1, N1f), где N1=1, а N1f в результате доказательства получила значение 1. В свою очередь, этот предикат был вызван из тела предиката fact при N=2. Так как предикат fact(N1, N1f) доказан, доказывается следующий предикат: Nf=N1f*N. В результате переменная Nf получает значение 2. Таким образом, доказан предикат fact(N, Nf) с аргументом N=2, вызванный из тела целевого предиката. Переменная Nf получила значение 2.
Примечание: Можно сказать, что рекурсивные вызовы предикатов в рекурсивных программах образуют стек.
Необходимо обратить внимание, что для уменьшения переменной на единицу используется запись N1=N-1. Запись типа N=N-1 в Прологе недопустима, так как она рассматривается как предикат сравнения N и N-1; очевидно, что он всегда принимает значение «ложь». Присвоить связанной переменной новое значение в Прологе невозможно.
Результат выполнения программы:
1-й случай (рис. 2):
N=1
F=1
Рис. 2. Вывод результата программы (1 случай)
2-й случай (рис. 3):
N=2
F=2
Рис. 3. Вывод результата программы (2 случай)
3-й случай (рис. 4):
N=4
F=24
Рис. 4. Вывод результата программы (3 случай)
Пример 3. Ханойские башни
Имеется три стержня: A, B и C. На стержне А надеты N дисков разного диаметра, надетые друг на друга в порядке убывания диаметров. Необходимо переместить диски со стержня А на стержень С, используя В как вспомогательный, если перекладывать можно только по одному диску и нельзя больший диск класть на меньший.
Решение:
Составим правило move, определяющее порядок переноса дисков.
Нерекурсивная часть правила определяет действие, если на стержне находится 1 диск.
Рекурсивная часть правила перемещает сначала верхние (N-1) диск на стержень B, используя С как вспомогательный, затем оставшийся диск на стержень C и, наконец, диски со стержня B на C, используя А как вспомогательный.
PREDICATES
move(integer, char, char, char)
CLAUSES
move(1, A, B, C):- write(“Perenesti disk c”, A, “ na ”, C),nl, !.
move(N, A, B, C):- M=N-1, move(M, A, C, B), write(“Perenesti disk c”, A, “ na ”, C), nl, move(M, B, A, C).
GOAL
write(“Xanoiskie bashni”), nl, write(“Kolichestvo diskov:”), readint(N), nl, move(N, ‘A’, ‘B’, ‘C’).
Результат выполнения программы (рис. 5):
Ханойские башни
Количество дисков: 3
Перенести диск с A на C
Перенести диск с A на B
Перенести диск с C на B
Перенести диск с A на C
Перенести диск с B на A
Перенести диск с B на C
Перенести диск с A на C
Рис. 5. Вывод результата программы «Ханойские башни»
