Автор работы: Пользователь скрыл имя, 07 Июня 2014 в 16:59, курсовая работа
Процедурные (ИМПЕРАТИВНЫЕ) языки программирования требуют полного описания последовательности шагов (команд), которые нужно предпринять, чтобы решить задачу . К ним относятся СИ, ПАСКАЛЬ, АССЕМБЛЕР.
ПРОЛОГ — язык ДЕКЛАРАТИВНЫЙ. Он базируется на естественных для человека логических принципах. Нужно уметь составить формальное описание задачи, используя понятия объектов различных типов и отношений между ними. Иными словами, нужно описать все ФАКТЫ (ИСТИННЫЕ УТВЕРЖДЕНИЯ) и ПРАВИЛА (позволяющие ВЫВЕСТИ из уже имеющихся истинных утверждений новые), описывающие данную ситуацию. Затем пользователь задает вопрос или, пользуясь терминологией Пролога, задает ЦЕЛЬ.
repeat:-repeat.
% выход из цикла для точки (0,0)
pos_point(0,0,_):-!.
pos_point(X,Y,R):-
out_sqr(X,Y,R),!,
write(" вне квадрата"), nl,
fail.
pos_point(X,Y,R):-
in_circ(X,Y,R),!,
write("внутри круга"), nl,
fail.
pos_point(_,_,_):-
!,
write("вне круга и внутри
nl, fail.
out_sqr(X,Y,R):- % условие вне квадрата
abs(X)>R;
abs(Y)>R.
in_circ(X,Y,R):- % условие внутри круга
X*X+Y*Y<R*R.
/*
Конец программы
Подсчитать число точек является делом более сложным, так как при возвратах переменные будут «забывать» накопленные значения, а глобальных переменных у нас нет.
Для того, чтобы решить эту задачу, не отказываясь от цикла repeat, нужно воспользоваться динамической базой данных, которая играет роль «глобальной переменной».
Работа с динамической базой данных будет рассмотрена позже, а пока отметим, что такой метод организации цикла наиболее эффективен при реализации доступа к данным в базе данных и файлах на диске, а также для организации меню.
Подобным же образом в правиле можно использовать более одного правила повтора. Ниже приведено правило, включающее два правила повтора:
do_two_things :-
repeat1,
<повторяемое тело>,
<условие выхода 1>,!,
repeat2,
<повторяемоу тело>,
<условие выхода 2>,!.
repeat1.
repeat1 :- repeat1.
repeat2.
repeat2 :- repeat2.
Упражнение 4.3.
На числовую прямую с тремя отмеченными областями (от —¥ до —1, от —1 до 2 и от 2 до +¥). Показать, куда падает точка.
Упражнение 4.4.
Имеется база данных о результатах партий теннисного матча. Результаты представлены в программе в виде фактов типа:
win(tom,john).
Определить отношение class, которое будет распределять игроков по категориям:
profi — победитель всех сыгранных им матчей;
player —выигравший и проигравший хотя бы одну игру;
user — проигравший все матчи.
Если игрок отсутствует в базе, программа должна выдавать соответствующее сообщение.
Правило, содержащее само себя в качестве компоненты, называется ПРАВИЛОМ РЕКУРСИИ. Правила рекурсии так же, как правила повтора, реализуют повторное выполнение задач. Они весьма эффективны, например, при формировании запросов к базе данных, а также при обработке таких доменных структур, как списки. Этот метод подходит для целого ряда применений, начиная с обработки файлов и кончая математическими вычислениями.
Если рекурсивное правило не генерирует указателей возврата, и последняя подцель правила является рекурсивным вызовом самого правила, то Турбо-Пролог устранит дополнительные расходы, вызываемые рекурсией.
Этот процесс называется УСТРАНЕНИЕМ ХВОСТОВОЙ РЕКУРСИИ.
Пример простого правила рекурсии:
write_string :- /* выдать строку */
write("У ПОПА БЫЛА СОБАКА..."),
nl,
write_string.
Это правило будет бесконечно вызывать само себя.
Избежать возникновения бесконечной рекурсии можно. Для этого следует ввести предикат завершения, содержащий условие выхода.
Формулировка условия выхода на русском языке для правила write_string может иметь вид: «Продолжать печать строки до тех пор, пока счетчик печати не превысит число 7. После чего остановить процесс».
Правило read_a_char демонстрирует простое правило рекурсии, в которое включено условие выхода. Программа циклически считывает символ, введенный пользователем: если этот символ не #, то он выдается на экран, если этот символ — #, то программа завершается.
Простое правило рекурсии с условием выхода:
read_a_char :-
readchar(Ch),
Ch <> '#',
write(Ch),
read_a_char.
Обобщенное правило рекурсии также содержит в теле правила само себя. Рекурсия будет конечной, если в правило включено условие выхода, гарантирующее окончание его работы. Ответственность за обеспечение завершаемости правила рекурсии лежит на программисте.
Тело правила состоит из утверждений и правил, определяющих задачи, которые должны быть выполнены. Ниже в символическом виде дан общий вид правила рекурсии:
<имя правила рекурсии> :-
(1) <список предикатов>,
(2) <предикат условия выхода>,
(3) <список предикатов>,
(4) <имя правила рекурсии>,
(5) <список предикатов>.
Данное правило рекурсии имеет пять компонент. Первая — это группа предикатов. Успех или неудача любого из них на рекурсиию не влияет.
Следующая компонента — предикат условия выхода. Успех или неудача этого предиката либо позволяет продолжить рекурсию, либо вызывает ее остановку. Третья компонента — список других предикатов. Аналогично, успех или неудача этих предикатов на рекурсию не оказывает влияния.
Четвертая группа — само рекурсивное правило. Успех этого правила вызывает рекурсию.
Пятая группа — список предикатов, успех или неудача которых не влияет на рекурсию. Пятая группа может использовать значения, помещенные в стек во время выполнения рекурсии.
Правила, построенные указанным образом, являются обобщенными правилами рекурсии (ОПР), а метод называется ОПР-методом.
Например, вы хотите написать правило генерации всех целых чисел, начиная с 1 и кончая 7. Пусть имя правила будет write_number(Number).
write_number(8).
write_number(Number) :-
Number < 8,
write(Number), nl,
Next_Number = Number + 1,
write_number(Next_number).
Для этого примера первая компонента структуры общего правила рекурсии не используется. Второй компонентой, т.е. предикатом выхода, является Number < 8. Когда значение Number равно 8, правило будет успешным и программа завершится.
Третья компонента правила оперирует с числами. В этой части правила число выдается на экран и затем увеличивается на 1.
Для увеличенного числа будет использоваться новая переменная Next_Number. Четветая компонента — вызов самого правила рекурсии write_number(Next_number). Пятая компонента, представленная в общем случае, здесь не используется.
Программа начинается с попытки вычислить подцель write_number(1). Заметим, что необязательно вызывать правило, используя то же имя переменной, что используется в голове правила. Это всего лишь позиция в списке параметров, имеющая значение при передаче значений. Фактически, если не передавать значение Next_Number, то приращение основного числа программы невозможно. При рекурсивном вызове головы правила, программа снова пытается выполнить подцель write_number(8). Программа продолжает выполнять цикл сопоставления, присвоения и выдачи значений Number до тех пор, пока значение Number не станет равным 8. В этот момент цель выполнена, правило успешно и программа завершается.
Упражнение 5.1.
Измените правило рекурсии write_string так, чтобы результатом программы была семикратная печать строки
Используем правило рекурсии для вычисления суммы ряда целых чисел от 1 до 7:
S(7) = 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28
Правило рекурсии программы выполняет вычисления по обычной схеме сложения:
частичная сумма + следующее значение.
Правило 1 рекурсии имеет вид:
sum_series(1,1). /* сумма1 ряда */
sum_series(Number,Sum) :-
Number > 1,
Next_number = Number — 1,
sum_series(Next_number,
Sum = Number + Partial_Sum.
Данное правило имеет четыре компоненты и одно дополнительное нерекурсивное правило. Заметим, что последняя компонента правила рекурсии — это правило Sum с Partial_Sum (частичная сумма) в качестве переменной. Это правило не может быть выполнено до тех пор, пока Partial_Sum не получит некоторого значения.
Программа Сумма1 начинается с попытки выполнить подцель sum_series(7,Sum). Сначала программа пытается сопоставить подцель с подправилом sum_series(1,1). Сопоставление неудачно. Затем она пытается сопоставить подцель с sum_series(Number,Sum). На этот раз сопоставление завершается успешно с присвоением переменной Number значения 7. Затем программа сравнивает значение Number, которое равно 7, с 1 т.е. проверяется условие выхода. Так как 7 больше 1, то сопоставление успешно, программа переходит к следующему подправилу. Для этого подправила переменной Next_number присвоено значение 6, т.е. значение Number — 1. Затем правило вызывает само себя в виде sum_series(6,Partial_Sum). Следующим подправилом является правило Sum, содержащее свободную переменную Partial_Sum. Оно не может быть вызвано до тех пор, пока Partial_Sum не получит значения.
Теперь программа пытается сопоставить факт sum_series(1,1) с sum_series(6,Partial_Sum). Процесс сопоставления неуспешен, поскольку несопоставим ни один из параметров.
В результате программа переходит к следующему правилу с головой sum_series(Number,Sum), присваивая переменной Number значение 6. Этот циклический процесс сопоставления продолжается до тех пор, пока не будет получено sum_series(1,Partial_Sum). Теперь это правило сопоставляется с sum_series(1,1), а Partial_Sum приписывается значение 1. Переменная Sum получает, наконец, значение 3 (1+2).
Во время процесса сопоставления переменная Partial_Sum была свободна, а программа запоминала значения Number для последующего использования. Теперь эти значения извлекаются из стека, а Sum получает последовательно значения 6,10,15,21 и 28. Конечное значение Sum есть 28.
При возвратах, когда цель sum_series(1,Partial_Sum) сопоставится с правилом sum_series(Number,Sum) условие Number > 1 не даст
Next_number получить значение —1, а программе войти в бесконечную рекурсию.
/* Программа 5.1 «Сумма ряда». Назначение:*/
/*демонстрация использования рекурсивного */
/*предиката подсчета суммы S(N) ряда S, */
/* где N положительное целое число */
domains
number, sum = integer
predicates
sum_series(number, sum)
goal
sum_series(7,Sum),
write("Сумма ряда:"),nl,nl,
write(" S(7) = ", Sum), nl.
clauses
sum_series(1,1). /* сумма1 ряда */
sum_series(Number,Sum) :-
Number > 0,
Next_number = Number — 1,
sum_series(Next_number,
Sum = Number + Partial_Sum.
/*
Конец программы
Заменим правило sum_series на правило sum_series2. Правило sum_series2 является модификацией правила sum_series. Модификация выполнена посредством удаления условия выхода Number > 1 и введения правила
sum_series2(1,1) :- !.
вместо факта
sum_series(1,1).
Правило sum_series2 рекурсии:
sum_series2(1,1) :- !. /* сумма2 ряда */
sum_series2(Number,Sum) :-
Next_number = Number — 1,
sum_series2(Next_number,
Sum = Number + Partial_sum.
Результаты работы этих правил идентичны. Их следует рассматривать как альтернативные варианты. Сразу возникает вопрос: где во втором правиле условие выхода?
В самом общем виде рекурсия есть способ разбиения задачи на подзадачи, каждая из которых является уменьшенным вариантоми предыдущей. В итоге мы должны прийти к задаче, решаемой непосредственно.
Решить ее позволит утверждение, называемое ГРАНИЧНЫМ УСЛОВИЕМ.
В задаче о вычислении суммы таким условием является
sum_series2(1,1) :- !.
Для ответа на запрос sum_series2(4,X) последовательно вычислялись sum_series2(3,S1), sum_series2(2,S2), sum_series2(1,S3) (граничное условие).
Такая рекурсия называется НИСХОДЯЩЕЙ.
Поступим наоборот: начнем с граничного условия и будем строить решение, пока не получим исходную задачу.
Такая рекурсия называется ВОСХОДЯЩЕЙ.
В таком методе правило sum_series3 должно иметь два дополнительных параметра:
1) указание на размер решенной к данному моменту задачи (число уже просуммированных слагаемых);
2) запись промежуточного решения (уже подсчитанной частичной суммы).
Запрос
sum_series3(1,1,7,X)
включает старое граничное условие, как уже решенную к настоящему моменту задачу.
Новое граничное условие выглядит так:
sum_series3(N,X,N,X):-!.
Правило 3 рекурсии:
/* M-текущее слагаемое, S-частичная сумма, N-последнее слагаемое, X-окончательная сумма */
sum_series3(M,S,N,X):-
M1=M+1,
S1=S+M1,
sum_series3(M1,S1,N,X).
Упражнение 5.2.
Вычислить и напечатать факториал числа 5 с помощью восходящей и нисходящей рекурсии.
/* Программа 5.2 «Подсчет точек». Назначение: */
/*демонстрация
использования восходящей
constants
r=2
domains
i=integer
r=real
predicates
show_point
culc_point(i,i,i,i,i,i)
pos_point(i,i,i,r,r,i,i,i)
out_sqr(r,r)
in_circ(r,r)
goal
show_point.
clauses
show_point:-
clearwindow,
% запрос с обнуленными
culc_point(0,0,0,Sum1,Sum2,
write("вне квадрата ",Sum1,
"внутри круга ",Sum2,
"в квадрате вне круга ",Sum3).
% рекурсивное правило
culc_point(C1,C2,C3,Sum1,Sum2,
write(" X? "),readreal(X),
write(" Y? "),readreal(Y),
pos_point(C1,C2,C3,X,Y,S1,S2,
% вызов с новыми значениями счетчиков
culc_point(S1,S2,S3,Sum1,Sum2,
% выход из рекурсии с
culc_point(C1,C2,C3,C1,C2,C3).
% окончание запросов и отказ процедуры
% pos_point
pos_point(_,_,_,0,0,_,_,_):-!,
% увеличение первого счетчика
pos_point(C1,C2,C3,X,Y,S1,C2,
S1=C1+1,
out_sqr(X,Y),!,
write(" вне квадрата").
% увеличение второго счетчика
pos_point(C1,C2,C3,X,Y,C1,S2,
S2=C2+1,
in_circ(X,Y),!,
write("внутри круга").
% увеличение третьего счетчика
pos_point(C1,C2,C3,_,_,C1,C2,
S3=C3+1,
write("вне круга и внутри
% условие для точки вне квадрата
out_sqr(X,Y):-
abs(X)>r;
abs(Y)>r.
% условие для точки внутри круга
in_circ(X,Y):-
X*X+Y*Y<r*r.
/* Конец программы */
Программа 5.2 содержит восходящую рекурсивную процедуру culc_point, благодаря которой происходит подсчет точек.
Первый вызов ее из цели верхнего уровня —
culc_point(0,0,0,Sum1,Sum2,
Информация о работе Основы программирования на языке Turbo Prolog