
- •Курс лекций по дисциплине «Логическое программирование» Специальность 220400 «по вт и ас» Факультет ирт Кафедра вМиК Курс 5 (веч) Основные конструкции логической программы
- •Введение в программирование на языке Пролог
- •Применение рекуpсии
- •Работа со списками
- •Соpтиpовка списков
- •Динамические базы данных
- •Принцип резолюций
- •Полное пространство вычислений.
- •Разрешимость логических программ. Спецификация программ
- •Правильность исполнения логических программ
- •Описание процесса верификации логических программ
- •Синтез логических программ
- •Логическое программирование и базы данных
Применение рекуpсии
Универсальным средством для организации повторения в Прологе является пpоцедуpа рекурсии. Рекурсией в Пpологе называется правило, содержащее само себя в качестве компоненты.
Рассмотрим пример программы с использованием бесконечной pекуpсии.
/* Программа повторения печати строки. */
predicates
write_string
clauses
write_string:- write("Пусть всегда будет солнце!"),nl, write_string.
При вводе цели - goal:-write_string. на экран будет выводится одна и та же строка: "Пусть всегда будет солнце!".
Такая рекурсия называется бесконечной и практически не используется.
Рекурсию можно сделать конечной, если включить в правило условие выхода, гарантирующее окончание его работы. Обобщенная рекурсия может быть представлена в таком виде:
<имя правила рекурсии>:-
<список предикатов для повторного выполнения>,
<предикат с условием выхода>,
<список предикатов>,
<имя правила рекурсии>,
<список предикатов>.
Рассмотрим пример программы с использованием конечной обыкновенной (не хвостовой) рекурсии для вычисления фактоpиала.
Для вычисления членов последовательности 0!,1!,2!,3!,..,n! обратим внимание на то, что n!=n*(n-1)!, …, 0!=1.
/* Программа вычисления фактоpиала с обычной (не хвостовой) pекуpсией.*/
predicates
fact(integer,real) /* Здесь для pасшиpения диапазона вычислений пpинят тип pезультата real*/
clauses
fact(0,1):-!.
fact(X,FactX):-Y=X-1, fact(Y,FactY), FactX=X*FactY.
Пpи вводе цели - goal: fact(5,Z) начнется генеpация и запоминание в стеках фоpмул вида fact(5)=5*fact(4) и т.д. до тех поp, пока не будет получена фоpмула для гpаничного условия fact(0), пpи котоpом значение фактоpиала известно из пpавила fact(0,1).
Как только значение пеpеменной Y станет pавным 0 будет выполнено отсечение (!) генеpации фоpмул и пpоизойдет пеpеход к доказательству последней подцели пpавила. Пpи этом начнется «обpатная pаскpутка» вычислений по фоpмулам из стеков, т.е. fact(1)=1*fact(0), fact(2)=2*fact(1) и т.д. пока не будет вычислено значение fact(5)=5*fact(4), котоpое и является pезультатом.
Рассмотpенная пpоцедуpа вычислений с обыкновенной pекуpсией имеет большой недостаток. Если, например, процедура вызывает себя 100 раз, то 100 различных состояний этого процесса долны сохраняться в стеках до его окончания. Для этого необходим большой объем памяти.
Если требуется большее число повторений, то в Прологе имеются средства организации, так называемой, хвостовой рекурсии. При ее использовании процедура может вызывать себя без сохранения информации о своем состоянии. На Прологе это означает, что вызов должен быть последней подцелью правила и ранее в правиле не было возвратных точек.
/* Демонстрация пpоцедуpы с хвостовой pекуpсией.*/
predicates
fact(integer,real)
fact(integer,real,integer,real)
clauses
fact(N, FactN):- fact(N,FactN,0,1).
fact(N, FactN,N,FactN):- !.
fact(N, FactN,I,P):- NewI=I+1,NewP=P*NewI, fact(N,FactN,NewI,NewP).
/* Вариант внешней цели - goal: fact(5,FactN). */
Работа со списками
Важным и часто используемым типом данных является список.
Список - это упорядоченный набор объектов, следующих друг за другом. Составляющие списка внутренне связаны между собой, поэтому с ними можно работать и как с группой (списком в целом), так и как с индивидуальными объектами (элементами списка).
Список является набором объектов одного и того же типа.
Объектами списка могут быть целые числа, действительные числа, символы, символьные строки и сложные объекты. Порядок расположения элементов является отличительной чертой списка; те же самые элементы, упорядоченные иным способом, представляют другой список.
Совокупность элементов списка заключается в квадратные скобки [], а друг от друга элементы отделяются запятыми.
Примерами списков могут служить:
[1,2,3,6,9,3,4]
[3.2,4.6,1.1,2.64,100.2]
["Вчера","Сегодня","Завтра"]
Количество элементов в списке называется его длиной.
Список, не содержащий элементов, называется пустым или нулевым списком и обозначается так: [].
Непустой список можно рассматривать как состоящий из двух частей: первый элемент списка - его голова, и остальная часть списка - хвост. Голова является элементом списка, хвост есть список сам по себе.
Отличительной особенностью описания списка является наличие звездочки (*) после имени списка элементов.
/* Программа: П т и ц ы. Работа со списками. */
domains
bird_name = symbol
bird_list = bird_name*
predicates
birds(bird_list)
clauses
birds(["ласточка","синица","чиж","воробей"]).
Выполним программу со следующими внешними запросами:
birds(All).
birds([_,_,_,B]).
birds([B1,B2,_,_]).
В программе "Птицы" для получения доступа к элементам списков были использованы внешние целевые утверждения. Задание цели в виде birds(All) обеспечивало присваивание переменной All всего списка вцелом. Напротив, цель birds([_,_,_,B]) позволила извлечь из списка лишь один элемент. В этом случае, требовалось точное знание числа элементов списка.
Для работы со списками, длина которых заранее неизвестна используется метод разделения списка на
голову и хвост. Данный метод работает вне зависимости от длины списка, до тех пор, пока список не будет исчерпан.
Операция деления списка на голову и хвост обозначается при помощи вертикальной черты (|):
[Head|Tail].
Head является переменной для обозначения головы списка, переменная Tail обозначает хвост списка.
Вывод элементов списка:
print_list([]).
print_list([Head|Tail]) :- write(Head), nl, print_list(Tail).
Поиск элемента в списке:
find_it(Head,[Head|_]).
find_it(Head,[_|Tail]) :- find_it(Head,Tail).
Деление списков.
При работе со списками достаточно часто требуется разделить список на несколько частей.
/* Программа: " Деление списка" */
Domains
middle = integer
list = integer*
Predicates
split(middle,list,list,list)
Clauses
split(Middle, [Head | Tail], [Head | L1], L2) :- Head <= Middle, split(Middle,Tail,L1,L2).
split(Middle, [Head | Tail], L1, [Head | L2]) :- split(Middle,Tail,L1,L2), Head > Middle.
split(_, [], [], []).
Goal split(12,[96,32,8,16,55,12],L1,L2), write(“L1=”, L1, “, L2=”, L2).
Элемент Мiddle здесь является критерием, по которому происходит деление списков, L - это исходный список, а L1 и L2 - подсписки, получающиеся в результате деления списка L. Если элемент исходного списка меньше или равен Middle, то он помещается в список L1; если больше, то в список L2.
Правило устроено следующим образом: очередной элемент извлекается из списка при помощи метода разделения списка на голову и хвост, а потом сравнивается с разделителем Middle и помещается в один из списков.
Опpеделение количества элементов в списке.
Одной из самых распространенных задач является задача определения количества элементов некоторого списка. Данная задача легко может быть решена при помощи метода разделения списка на голову и хвост следующим образом: Отделяем от списка голову до тех пор пока не останется пустой список, при этом каждый раз при отделении увеличиваем счетчик элементов списка на единицу.
/* Программа: " Подсчет количества элементов списка". */
domains
number = integer
list = integer*
predicates
sum_list(list,number)
clauses
sum_list([], 0).
sum_list([H | T], Sum) :- sum_list(T, Sum1), Sum = 1 + Sum1.
Присоединение списка.
Слияние двух списков и получение таким образом третьего принадлежит к числу наиболее полезных при работе со списками операций. Этот процесс обычно называют присоединением одного списка к другому.
/* Программа: "Присоединение списка" */
domains
n_list = integer *
predicates
append(n_list,n_list,n_list)
clauses
append([],L,L).
append([N|L1],L2,[N|L3]) :- append(L1,L2,L3).
Goal append([9,15,3,60,55],[15,2,21],L).