Можно ли использовать продолжения вместо рекурсии?

StackOverflow https://stackoverflow.com/questions/2459116

  •  20-09-2019
  •  | 
  •  

Вопрос

Следующая функция генерирует «слишком глубокий уровень стека (SystemStackError)» для n = 5000.

def factorial(n)
  n == 0 ? 1 : factorial(n -1) * n
end

Есть ли способ избежать этой ошибки, используя continues/callcc?

Примечание:

Я знаю, что это можно реализовать без рекурсии.например

def factorial2(n)
  (1..n).inject(1) {|result, n| result * n } 
end
Это было полезно?

Решение

Конечно.Продолжения могут все!Однако в конечном итоге вам придется заново изобрести одну из двух вещей:цикл или вызов функции.На моей машине, которая по умолчанию выполняет оптимизацию хвостового вызова, хвостовая рекурсия с call/cc делает нет оптимизироваться.Программа быстро зависает при каждом callcc по-видимому, захватывает весь стек вызовов.Этот код сломан, вот цикл call/cc:

def fact( n )
    (n, f, k) = callcc { |k| [ n, 1, k ] }
    if ( n == 0 ) then return f
    else k.call n-1, n*f, k
    end
end

Примечание: ранее я забыл, что call/cc — это не просто инициация цепочки передачи продолжений, и запутался в необходимости рекурсии, отсюда и комментарии ниже.

Другие советы

Вы можете включить оптимизацию хвостового вызова с помощью tailcall_optimize :factorial, взято из здесь.

class Class
  def tailcall_optimize( *methods )
    methods.each do |meth|
      org = instance_method( meth )
      define_method( meth ) do |*args|
        if Thread.current[ meth ]
          throw( :recurse, args )
        else
          Thread.current[ meth ] = org.bind( self )
          result = catch( :done ) do
            loop do
              args = catch( :recurse ) do
                throw( :done, Thread.current[ meth ].call( *args ) )
              end
            end
          end
          Thread.current[ meth ] = nil
          result
        end
      end
    end
  end
end

Видеть этот интересный пост об определении, включена ли хвостовая рекурсия.

Проблема в том, что функция возвращает factorial(n -1) * n это выражение, а не одиночный рекурсивный вызов.Если вам удается иметь только вызов функции в операторе возврата, то функция вызывается хвостовая рекурсия, а хороший компилятор/интерпретатор (я не уверен, способен ли Ruby на это) может выполнить некоторые оптимизации, чтобы избежать использования стека.

Такая функция хвостовой рекурсии может выглядеть так, но обратите внимание, что я не программист Ruby, поэтому я не привык ни к синтаксису, ни к возможностям интерпретатора.Но вы можете попробовать это самостоятельно:

def factorial(n, result=1)
    n == 0 ? result : factorial(n-1, result * n)
end
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top