【问题标题】:Ruby left vs right recursionRuby左与右递归
【发布时间】:2014-06-17 03:41:36
【问题描述】:

由于某种原因,Ruby 在面对左递归时似乎表现得更好。例如:

def left_recursive_factorial(number)
  return 1 if number.zero?
  left_recursive_factorial(number.pred) * number
end

def right_recursive_factorial(number)
  return 1 if number.zero?
  number * right_recursive_factorial(number.pred)
end

当我用超过 9000 (????) 的值调用这些方法时,我会得到不同的结果:

left_recursive_factorial(9001)
  # => factorial of 9001

right_recursive_factorial(9001)
  # => SystemStackError: stack level too deep
  #    from (pry):6:in `right_recursive_factorial'

我找不到这种行为的任何解释。

唯一似乎有点相关的是LL() 解析器存在左递归问题,我想你可以翻转它,但我没有深入研究它。

有人可以更详细地解释一下导致左右递归执行不同的原因(通常,特别是在 Ruby 中),如果您有可能选择其中一个,为什么要选择它(以及为什么选择它在 Ruby 中)?

【问题讨论】:

  • 似乎 ruby​​ 在左侧之前计算乘法的右侧,因此左侧版本使用tail recursion,因此不必添加到堆栈中。
  • @clcto:这看起来不像是尾调用消除。一方面,乘法是函数中的最后一个操作,而不是递归调用。另一方面,如果你将数字增加到 9500,你仍然会破坏堆栈。
  • @clcto Ruby 最明确地从左到右而不是从右到左评估操作数和方法参数。此外,评估操作数的顺序与某事物是否为尾递归无关。乘法必然发生在函数调用之后(因为在知道两个数字之前,您不能将两个数字相乘),因此无论哪个数字首先被评估,该方法都不是尾递归的。无论哪种方式,标准 Ruby 解释器都不会优化尾递归。

标签: ruby parsing recursion internals


【解决方案1】:

好的,我不是任何类型的 YARV 黑客,但据我所知,这就是区别。当您调用一个方法时,发送方将方法的参数压入堆栈,然后被调用的方法将其参数弹出并压入其返回值。首先进行递归调用,number 参数尚未被压入堆栈,因此每次调用的堆栈占用的空间略少。这就是为什么您可以从该版本中获得更多迭代,但不会大幅增加——您看到的是堆栈使用量减少了几个百分点。

【讨论】:

  • 这是有道理的,但我希望得到更详细的答案。比如为什么python和php看起来无所谓,而js表现出类似的行为。
  • @ndn:这高度依赖于语言实现。例如,Python 有一个递归限制,这是解释器检查并在达到递归调用时拒绝递归调用的实际值。您可以使用sys.setrecursionlimit() 更改它。在 PHP 的情况下,我不得不去拿一个反汇编程序,因为我不知道它是如何工作的。它不采用压入参数然后调用乘法的堆栈机方法。相反,它实际上将* 的操作数作为MUL 操作码的操作数,因此函数之间的唯一区别是MUL !0, $2MUL $2, !0
猜你喜欢
  • 1970-01-01
  • 2014-07-13
  • 1970-01-01
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多