假设我有以下代码:
def rcall(num)
return 0 if 10 == num
1 + rcall(num - 1)
end
p rcall(90) # => 80
此代码将始终返回比传递给num
的值小10的值,即进行的递归调用的计数。
我看不到它是如何工作的。我有一个模糊的理解,如果满足退出条件,我们将返回零,以免再次增加计数器。但是,确切地讲,在proc调用中加1会如何增加调用的次数?我看不到增量器在哪里累积。
此外,这是特定于Ruby体系结构的技术,还是更普遍地适用?在回答有关如何计算递归调用的问题的任何答案中,我都没有看到它。似乎大多数时候,人们都会传递一个计数器变量来跟踪计数。
答案 0 :(得分:3)
假设您将基本条件更改为return 0 if num.zero?
,然后使用参数3调用它,如下所示:
1 + rcall(2)
# => 1 + rcall(1)
# => 1 + rcall(0)
# => 0
如果您用其结果替换rcall
调用(从底部开始),则会显示到1 + 1 + 1 + 0
换句话说,从基础案例开始,您可以更轻松地理解它:
rcall(0)
# 0
rcall(1)
# the same as 1 + rcall(0), which is one
rcall(2)
# the same as 1 + rcall(1), which is two.
希望您能看到图案。
正如评论中提到的,您可以像这样对它进行优化:
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
def rcall(num, sum=0)
return sum if 10 == num
rcall(num - 1, sum + 1)
end
尽管这可能需要其他设置,但我不确定。
答案 1 :(得分:1)
递归是很多人都知道的事情之一,但是没有多少人可以形容它值得该死(包括我自己)。这是几种语言(不仅仅是Ruby)支持的一种通用编程方法。
任何给定的调用都会在再次调用该方法时隐藏其当前状态,因为它需要一个值来继续。只有最深的级别返回一个值(在这种情况下为0)时,整个东西才能解开,因为先前的调用现在具有可以使用的值(每个值加1)。之所以减少10,是因为您使用10作为“完成”值,实际上跳过了所有比保护比较小的值(例如,将比较设置为0或负数)。
在这种情况下,保护测试避免了“堆栈级别太深”错误,因为没有其他方法可以防止递归失控-您可以看到,使用小于比较值的初始值,例如开始比较为10时为5。
答案 2 :(得分:1)
我刚刚在您的代码中添加了puts
,也许比我用英语能解释的更好地遵循了逻辑。
因此,这是经过调整的代码:
def rcall(num)
if 10 == num
rec = 0
puts "rec = #{rec} ---- End of recursion"
return rec
end
rec = rcall(num - 1)
res = 1 + rec
puts "rec = #{rec} \t\t res = i + rec = #{res}"
res
end
例如,当您致电15
时,您会得到:
rcall(15)
# rec = 0 ---- End of recursion
# rec = 0 res = i + rec = 1
# rec = 1 res = i + rec = 2
# rec = 2 res = i + rec = 3
# rec = 3 res = i + rec = 4
# rec = 4 res = i + rec = 5
# 5
如果调用的数字小于10
,则永远不会到达递归的结尾,因此不会返回任何值来构建“调用堆栈”,并且会引发错误:stack level too deep (SystemStackError)
我还想分享以下有关在YouTube上递归的 Computerphile视频:https://youtu.be/Mv9NEXX1VHc
答案 3 :(得分:1)
通常情况并非如此。
重要的部分是rcall
在不进行递归调用时始终返回0。
因此,您可以在递归调用时将1加到rcall
上。
您为每个递归调用获得+1,并且当递归调用停止时+1链也会停止。
这也可以计算递归调用的数量,但永远不会停止计数:
def keep_counting
2 + keep_counting + keep_counting
end