【问题标题】:Can continuations be used as a replacement for recursion?延续可以用作递归的替代品吗?
【发布时间】:2011-01-28 09:11:39
【问题描述】:

以下函数为 n = 5,000 生成“堆栈级别太深 (SystemStackError)”

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

有没有办法使用 continuations/callcc 来避免这个错误?

注意:

我知道这可以在没有递归的情况下实现。例如

def factorial2(n)
  (1..n).inject(1) {|result, n| result * n } 
end

【问题讨论】:

  • 您不需要注入参数,它默认为调用它的 Enumerable 的第一个元素。同样使用支持 Symbol#to_proc (1.8.7+, 1.8.6 + Facets) 的 Ruby,您可以像这样编写 factorial2:(1..n).inject(&:*)

标签: ruby recursion continuations


【解决方案1】:

当然。延续可以做任何事情!但是,您最终会重新发明两件事之一:循环或函数调用。在我的机器上,默认情况下进行尾调用优化,使用 call/cc 的尾递归确实 not 得到优化。由于每个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 不仅仅是启动一个延续传递链,并且对递归的需要感到困惑,因此下面的 cmets。

【讨论】:

  • 或者更好的是,不要使用递归来解决阶乘问题。这根本没有意义。
  • @Billy:确实。但在尝试任何新的控制流构造时,这是一个很好的模型问题。
  • 我发现 n = 10,000 的注入速度更快,但 n = 100,000 的持续速度更快。
  • @Sam:真好奇!我看到使用这些实现与 f = 1; 100000.times { |i| i != 0 && ( f *= i ) } 或 while 循环中的任何一个实现 2x 加速。
【解决方案2】:

您可以使用tailcall_optimize :factorial 启用尾调用优化,取自here

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

有关确定是否启用尾递归,请参阅this interesting post

【讨论】:

  • 每次我觉得自己“看到了大象”时,都会出现一个 Ruby 的 sn-p,让我觉得自己像新兵一样年轻。
  • 这个解决方案对我不起作用。请发布完整的解决方案。
  • 我似乎无法很好地格式化这个,所以这里是丑陋的文本。 class Fact def fact(n, acc) if n==1 acc else fact(n-1, n*acc) end end tailcall_optimize :fact def factorial(n) fact(n,1) end end
【解决方案3】:

问题是,该函数返回factorial(n -1) * n,它是一个表达式并且没有单个递归调用。如果你设法在 return 语句中只调用函数,那么该函数被称为 tail recursive,一个好的编译器/解释器(我不确定 Ruby 是否有能力)可以做一些避免使用堆栈的优化。

这样的尾递归函数可能看起来像这样,但请注意,我不是 Ruby 程序员,所以我既不习惯语法也不习惯解释器的功能。但你也许可以自己尝试一下:

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

【讨论】:

  • 好的,Ruby 不(总是)进行尾调用优化。因此,在某些 ruby​​ 实现中,此代码可能有效,而在其他实现中则无效……请参见此处:stackoverflow.com/questions/824562/…
  • 这取决于编译器/解释器。 Ruby 不保证尾调用优化,因为 MRI Ruby 本身不是尾递归的。像 MacRuby 这样的实现应该可以做到。
  • 虽然我对延续的理解很薄弱,你能否解释一下尾递归调用与延续的关系,因为我认为它们没有那么相关?
  • 此实现适用于 n
  • @KalebPederson 尾调用尾递归函数的延续,因为调用者有效地将控制权交给了被调用者。
猜你喜欢
  • 1970-01-01
  • 2015-09-22
  • 2018-09-10
  • 2022-08-15
  • 2015-06-12
  • 2016-09-13
  • 1970-01-01
  • 2011-03-16
  • 2020-03-30
相关资源
最近更新 更多