【问题标题】:Trying to solidify tail recursion understanding in Scala试图在 Scala 中巩固对尾递归的理解
【发布时间】:2019-02-18 17:36:24
【问题描述】:

我正在复习 Scala 考试,并试图找出我错过的这个测验问题。我将尾递归理解为“最后一次调用本身”,但我对其中一些代码 sn-ps 之间的区别感到困惑。 为什么这被认为是尾递归,

def f(x: Int): Int = {
    if (x % 2 == 0) {1} 
    else {f(x + 1)}

但这不是吗?

def f(x: Int): Int = {
    if (x % 2 == 0) {1} 
    else {1 + f(x + 1)}

在函数中加 1 究竟是什么限制了它的尾递归?很抱歉,如果这是一个愚蠢的问题,我看不出这个数字有什么影响,并想巩固我的理解。任何其他完全能够识别尾递归的指针也很棒!

【问题讨论】:

标签: scala tail-recursion


【解决方案1】:

你的理解不太对。尾递归并不意味着“最后一次调用是它自己”,而是“调用自己是最后一次”。也就是说,尾递归调用必须是函数执行的最后一个操作。它必须是函数在该代码路径上执行的操作列表的“尾部”。 (当然,必须有一个不包含递归调用的代码路径)。

考虑如何评估表达式中的值而不是它们在代码中出现的顺序也很重要。这个表达式

1 + f(x + 1)

按此顺序执行:

tmp1 = x + 1
tmp2 = f(tmp1)
res = 1 + tmp2

或者,

add(1, f(add(x, 1))

这样写你可以看到对f的调用之后是另一个动作,最后的+/add。由于递归调用不是最后一个动作,所以不是尾递归。

【讨论】:

  • 谢谢你 - 澄清字面真的帮助了我!
【解决方案2】:

在第二个 sn-p 中,last call 不是“自身”,而是+。 您必须首先从f(x+1) 获取结果,然后然后 将其加1,然后再返回。因此,当前帧必须保留在堆栈上以供 f(x+1) 调用。

反之,在第一个sn-p中,f(x+1)被调用后就没有什么可做的了,因为它的结果变成了返回值。因此,编译器可以在调用之前从堆栈中删除当前帧,这样f(x+1) 调用的结果将直接返回给f(x) 的调用者。

【讨论】:

  • 谢谢!!我也很欣赏解释堆栈帧
  • @MeganByers 实际上,编译器通常根本不会“调用”f,它只会使用现有的堆栈帧跳回到f 的入口点。然后这变成了一个简单的循环,编译器可以按照通常的方式对其进行优化。
【解决方案3】:

用函数调用符号明确地写下所有内容通常会有所帮助:

def f(x: Int): Int =
  if (==(%(x, 2), 0) 1 
  else f(+(x, 1))

def f(x: Int): Int =
  if (==(%(x, 2), 0) 1 
  else +(1, f(+(x, 1)))

你现在能看出为什么在第二个例子中最后一个电话不是f吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-17
    • 2021-06-29
    • 2018-03-08
    • 2017-01-25
    • 1970-01-01
    • 2014-06-26
    相关资源
    最近更新 更多