#recursion #prolog #exponentiation
#рекурсия #пролог #возведение в степень
Вопрос:
Я не думаю, что понимаю, как работает рекурсия в prolog
Следующий код (степенная функция)
pow(_,0,1).
pow(X,Y,Z) :-
Y1 is Y - 1 ,
pow(X,Y1,Z1) ,
Z is Z1*X
.
Создает следующую трассировку:
[trace] ?- pow(2,2,X).
Call: (6) pow(2, 2, _G368) ? creep
Call: (7) _G444 is 2 -1 ? creep
Exit: (7) 1 is 2 -1 ? creep
Call: (7) pow(2, 1, _G443) ? creep
Call: (8) _G447 is 1 -1 ? creep
Exit: (8) 0 is 1 -1 ? creep
Call: (8) pow(2, 0, _G446) ? creep
Exit: (8) pow(2, 0, 1) ? creep
Call: (8) _G450 is 1*2 ? creep
Exit: (8) 2 is 1*2 ? creep
Exit: (7) pow(2, 1, 2) ? creep
Call: (7) _G368 is 2*2 ? creep
Exit: (7) 4 is 2*2 ? creep
Exit: (6) pow(2, 2, 4) ? creep
Я не понимаю, как работает последнее состояние: «Z равно Z1 * X». Когда вызывается эта функция? Когда достигается базовый вариант?
Как вообще вызывается базовый вариант?
Спасибо
Комментарии:
1. кстати, в вашем pow отсутствует сокращение в первом предложении. То, как это написано, приведет к бесконечной рекурсии, если вы проинструктируете prolog попытаться найти больше решений после того, как будет найдено первое.
Ответ №1:
Суть в том, что pow
это не функция. Это предикат. На самом деле Prolog не оценивает pow
, он пытается выполнить свои условия.
И когда достигается первое предложение? Это повторяется каждый раз. Но если второй аргумент не равен 0, а третий не равен 1 (или они являются переменными, которые могут быть объединены с этими значениями), он завершается с ошибкой. И когда первое предложение завершается неудачей, выполняется попытка второго.
Комментарии:
1. Большое спасибо, но я понял, что моя главная проблема заключается в понимании того, когда достигнут последний аргумент (Z). Не могли бы вы подробнее остановиться на этом?
2. Допустим, вы вводите что-то вроде
pow(2,3,Res)
. Первое предложение явно ложно (3 != 0). При попытке выполнения второго предложения сначала вычисляется переменнаяY1
. Затемpow
используется рекурсивно, присваивая значениеZ1
. Наконец, вычисляется значениеZ
.
Ответ №2:
Вы можете думать о pow
функции, которая разделена на два предложения, которые имеют дело с разными значениями параметров. Функция является рекурсивной, которая запускается рекурсивным вызовом во втором предложении. Но после этого вызова все еще есть что-то, что нужно сделать, Z is Z1*1
цель. Эти «зависшие» вычисления выполняются, когда рекурсия завершается, и снова управляют «пузырьками» вверх, так сказать, на обратном пути. (Для такого рода рекурсии есть название, которое я не могу вспомнить).
Посмотрите на эту прокомментированную трассировку:
[trace] ?- pow(2,2,X).
% initial call
Call: (6) pow(2, 2, _G368) ? creep
% the second clause is picked for this call,
% the third argument is an uninstantiated variable, represented by _G368
Call: (7) _G444 is 2 -1 ? creep
% the first goal in this claus is "Y1 is Y -1", which is here
% translated with its bindings
Exit: (7) 1 is 2 -1 ? creep
% the is/2 goal has been called, and has provided a binding for "Y1"
Call: (7) pow(2, 1, _G443) ? creep
% this is the first recursive call, with the new arguments 2, 1 and an
% undefined Z1
Call: (8) _G447 is 1 -1 ? creep
% again the second clause is used, this is the first goal in it,
% calling is/2
Exit: (8) 0 is 1 -1 ? creep
% is/2 delivers a binding for the current Y1, 0
Call: (8) pow(2, 0, _G446) ? creep
% the next (second) recursive call; see how at this point non of the
% "Z is Z1*X" "statements" have been reached
Exit: (8) pow(2, 0, 1) ? creep
% the second recursive call matches the first clause; this is where
% the base case is used! it can immediately "Exit" as with the match
% to the clause all bindings have been established already; the third
% argument is instantiated to 1
Call: (8) _G450 is 1*2 ? creep
% now the recursion has terminated, and control is passed back to that
% more recent calling clause (this was the one where Y1 has been bound
% to 0); now the "Z is Z1*X" can be tried for the first time, and Z
% can be instantiated ("unified")
Exit: (8) 2 is 1*2 ? creep
% this is the result of this unification, Z is bound to 2;
% with this, this level in the stack of recursive calls has been completed...
Exit: (7) pow(2, 1, 2) ? creep
% ... and this is the result ("Exit") of this call, showing all
% instantiated parameters
Call: (7) _G368 is 2*2 ? creep
% but this just brings us back one more level in the call stack, to a
% pending execution (this was the one where Y1 has been bound to 1),
% now the pending execution can be performed
Exit: (7) 4 is 2*2 ? creep
% here you see the result of the execution of is/2, binding Z to 4
Exit: (6) pow(2, 2, 4) ? creep
% and this finishes the initial call of the predicate, delivering a
% binding for the X in the query, 4; you can tell that the recursive
% call stack as been processed completely by looking at the "stack
% depth indicator", (6), which matches the initial (6) when the trace
% started (they don't necessarily start with 0 or 1).
Комментарии:
1. Я думаю, что понимаю, но кое-что все еще немного неясно, почему _G446 создает экземпляр до 1? Потому что там два других параметра соответствуют факту, а последний параметр ‘_G446’ деинсталлирован? Если это так, то я понимаю! Еще раз спасибо за объяснение
2. «Потому что там два других параметра соответствуют факту, а последний параметр»_G446″ деинсталлирован?» — Точно.
Ответ №3:
Каждая строка в трассировке со звездочкой (*) использует правило «Z равно Z1 * X».
Этот код работает, предоставляя следующее рекурсивное определение степенной функции:
- X^ 0 = 1 для всех X.
- X^Y = X^(Y-1) * X
Переменные Z, Z1 и Y1 являются артефактами того факта, что Prolog нуждается в способе ссылки на промежуточные результаты; вы вызываете Y-1 Y1 и вызываете X ^(Y-1) Z1 .
Это приводит к базовому варианту путем уменьшения показателя степени (Y) на единицу (что дает Y1) на каждом уровне рекурсии до тех пор, пока Y = 0 и применяется первый случай определения.
Комментарии:
1. Как насчет последнего параметра в базовом варианте? pow(_,0,1). Я понимаю, что первый параметр является анонимным, поэтому нас это не волнует, второй параметр очевиден, но я не понимаю, как достигается значение ‘1’. Спасибо!
2. pow(X,0,1) — это факт. Это означает, что любое значение X, которое вы принимаете в степени 0, дает 1. если вы используете только этот факт, вы можете задавать вопросы prolog, такие как: pow(48,0,X). и он ответит, что X = 1