Ответ 1
В рекурсивной функции каждый раз, когда происходит рекурсивный вызов, состояние вызывающего абонента сохраняется в стеке, а затем восстанавливается после завершения рекурсивного вызова. Чтобы преобразовать рекурсивную функцию в итеративную, вам нужно превратить состояние приостановленной функции в явную структуру данных. Конечно, вы можете создать свой собственный стек в программном обеспечении, но часто вы можете использовать трюки, чтобы сделать ваш код более эффективным.
Этот ответ работает с шагами преобразования для этого примера. Вы можете применить те же методы к другим циклам.
Трансформация ретрансляции хвоста
Еще раз взглянем на ваш код:
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