В рекурсивной функции каждый раз, когда происходит рекурсивный вызов, состояние вызывающего абонента сохраняется в стеке, а затем восстанавливается, когда рекурсивный вызов завершен.Чтобы преобразовать рекурсивную функцию в итеративную, необходимо превратить состояние приостановленной функции в явную структуру данных.Конечно, вы можете создать свой собственный стек в программном обеспечении, но часто есть приемы, которые вы можете использовать для повышения эффективности своего кода.
Этот ответ прорабатывает шаги преобразования для этого примера.Вы можете применить те же методы к другим циклам.
Преобразование хвостовой рекурсии
Давайте еще раз посмотрим на ваш код:
def Trace(ray):
# Here was code to look for intersections
if not hit:
return Color(0, 0, 0)
return hit.diffuse * (Trace(ray) + hit.emittance)
В общем, рекурсивный вызов имеетвернуться к вызывающей функции, чтобы вызывающий мог завершить то, что он делает.В этом случае вызывающая сторона «заканчивает», выполняя сложение и умножение.Это производит вычисление как d1 * (d2 * (d3 * (... + e3) + e2) + e1))
.Мы можем воспользоваться распределительным законом сложения и ассоциативными законами умножения и сложения, чтобы преобразовать вычисление в [d1 * e1] + [(d1 * d2) * e2] + [(d1 * d2) * d3) * e3] + ...
.Обратите внимание, что первый термин в этой серии относится только к итерации 1, второй относится только к итерациям 1 и 2 и т. Д.Это говорит нам о том, что мы можем вычислить эту серию на лету.Более того, эта серия содержит серию (d1, d1*d2, d1*d2*d3, ...)
, которую мы также можем вычислить на лету.Поместив это обратно в код:
def Trace(diffuse, emittance, ray):
# Here was code to look for intersections
if not hit: return emittance # The complete value has been computed
new_diffuse = diffuse * hit.diffuse # (...) * dN
new_emittance = emittance + new_diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
return Trace(new_diffuse, new_emittance, ray)
Устранение хвостовой рекурсии
В новом цикле вызывающей программе не нужно ничего делать после завершения вызываемого;он просто возвращает результат вызываемого.Вызывающему не нужно завершать работу, поэтому ему не нужно сохранять его состояние !Вместо вызова мы можем перезаписать старые параметры и вернуться к началу функции (не на Python, но это иллюстрирует суть):
def Trace(diffuse, emittance, ray):
beginning:
# Here was code to look for intersections
if not hit: return emittance # The complete value has been computed
new_diffuse = diffuse * hit.diffuse # (...) * dN
new_emittance = emittance + new_diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
(diffuse, emittance) = (new_diffuse, new_emittance)
goto beginning
Наконец, мы преобразовали рекурсивную функцию вэквивалентный цикл.Осталось только выразить его в синтаксисе Python.
def Trace(diffuse, emittance, ray):
while True:
# Here was code to look for intersections
if not hit: break
diffuse = diffuse * hit.diffuse # (...) * dN
emittance = emittance + diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
return emittance