Сумма ряда
Для начала рассмотрим пример суммирования первых n членов арифметической прогрессии. Хотя такую сумму можно легко вычислить с помощью конечной формулы, мы всё же посмотрим, как это будет выглядеть на Прологе в виде рекурсии.
Пример 6. Для вычисления суммы объявим предикат sum/3, в котором входными аргументами является первый член ряда From и количество суммируемых членов Count. Третий аргумент Summa является выходным. Шаг рекурсии для простоты равен 1.
class predicates
sum : (integer From, unsigned Count, integer Summa) procedure (i,i,o).
Рекурсивный предикат будет содержать два предложения:
clauses
sum(_,0,0):- !.
sum(V,Count,Summa) :- sum(V+1,Count-1,Summa1), Summa=Summa1+V.
Первое предложение является условием окончания рекурсии и содержит условие Count=0, для которого значение суммы равно нулю Summa=0. Это означает, что если ряд не содержит членов, то его сумма равна нулю.
Второе предложение собственно суммирует значения членов ряда с помощью предиката Summa=Summa1+V. Этот предикат можно выразить словами так: “сумма n членов ряда равна сумме n-1 членов ряда плюс n-й член ряда”. Основной особенностью такой рекурсии является то, что суммирование производится после рекурсивного вызова, другими словами – на выходе из рекурсии.
Программа имеет следующий код:
implement main
open core, console
constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".
class predicates sum : (integer From, unsigned Count, integer Summa) procedure (i,i,o).
clauses classInfo(className, classVersion).
sum(_,0,0):- !. sum(V,Count,Summa) :- sum(V+1,Count-1,Summa1), Summa=Summa1+V.
run():- init(), sum(1,3,Summa), write(Summa), write("\nПрограмма завершена"), _=readchar().
end implement main
goal
mainExe::run(main::run).
Запустив программу можно увидеть ответ, равный шести. Действительно, сумма первых трёх членов ряда 1+2+3 равна шести. Замечание: предикат sum/3 в этом примере можно записать более лаконично:
sum(_,0,0):- !.
sum(V,Count,Summa1+V) :- sum(V+1,Count-1,Summa1).
Однако в такой записи не очевидно то, что вычисления производятся на выходе из рекурсии.
Задание 9. Модифицировать предикат из примера 6 в предикат-функцию со следующим объявлением:
class predicates
sum : (integer From, unsigned Count, ) -> integer Summa procedure (i,i,o).
Пример 7. Суммирование значений членов ряда можно производить и перед рекурсивным вызовом, другими словами – на входе в рекурсию. Для этого в предикат sum/3 надо добавить ещё один входной аргумент Temp, в котором мы будем сохранять текущую сумму. Назовём новый предикат sum1/4 и объявим его так:
class predicates
sum1 : (integer From, unsigned Count, integer Temp, integer Summa) procedure (i,i,i,o).
При вызове этого предиката аргумент Temp должен быть равен нулю, так как он является текущей суммой.
sum1(_,0,Summa,Summa) :- !.
sum1(N,Count,Temp,Summa) :-
sum1(N+1,Count-1,Temp+N,Summa).
Полный текст программы выглядит так:
implement main
open core, console
constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".
class predicates
sum1 : (integer From, unsigned Count, integer Temp, integer Summa) procedure (i,i,i,o).
clauses
classInfo(className, classVersion).
sum1(_,0,Summa,Summa) :- !.
sum1(N,Count,Temp,Summa) :-
sum1(N+1,Count-1,Temp+N,Summa).
run():- init(),
sum1(1,3,0,Summa1),write("Ответ=",Summa1),nl,
write("\nПрограмма завершена"),
_=readchar().
end implement main
goal
mainExe::run(main::run).
Результат работы программы будет конечно же таким, как и в примере 6.
Пример 8. Этот пример демонстрирует различия между двумя способами вычисления суммы ряда: с одной стороны сумму можно подсчитать на выходе из рекурсии, как это сделано в примере 6; с другой стороны – на входе в рекурсию, как это сделано в примере 7. Второй способ расходует стек немного быстрее, чем первый. Для демонстрации размера используемой памяти применяется предикат memory::getUsedStack(), который возвращает размер используемого стека. Вызывая этот предикат перед рекурсивным вызовом и после рекурсивного вызова можно наглядно увидеть расход памяти на каждом витке цикла:
implement main
open core, console
constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".
class predicates sum : (integer From, unsigned Count, integer Summa) procedure (i,i,o). sum1 : (integer From, unsigned Count, integer Temp, integer Summa) procedure (i,i,i,o).
clauses classInfo(className, classVersion).
sum(_,0,0) :- write("Дно Stack=",memory::getUsedStack()," Summa=0"), nl,!. sum(V,Count,Summa1+V) :-
write("Вызов Stack=",memory::getUsedStack()),nl, sum(V+1,Count-1,Summa1), writef("Возврат Stack=% Summa=%+%=%", memory::getUsedStack(),Summa1,V,Summa1+V),nl.
sum1(_,0,Summa,Summa) :-
write("Дно Stack=",memory::getUsedStack(), " Summa=",Summa),nl,!. sum1(N,Count,Temp,Summa) :-
writef("Вызов Stack=% Summa=%+%=%", memory::getUsedStack(),Temp,N,Temp+N),nl, sum1(N+1,Count-1,Temp+N,Summa), write("Возврат Stack=",memory::getUsedStack()),nl.
run():- init(), sum(1,3,Summa),write("Ответ=",Summa),nl,nl, write("------------------------------"),nl,nl, sum1(1,3,0,Summa1),write("Ответ=",Summa1),nl, write("\nПрограмма завершена"), _=readchar().
end implement main
goal
mainExe::run(main::run).
Результаты работы этой программы демонстрируют различия между двумя способами рекурсий. Предикат sum/3 демонстрирует вычисления на выходе из рекурсии, а предикат sum1/4 – перед входом в рекурсию.
Задание 10. Разработать программу, вычисляющую сумму первых пяти случайных чисел, возвращаемых функцией Random=math::random(Max). Подсказка: 0 <= Random < Max.