【发布时间】:2019-11-13 02:24:37
【问题描述】:
我正在尝试使用@tailrec 计算每个子问题的结果
类似于正常递归解决方案如何为每个子问题生成解决方案。
以下是我处理的示例。
@tailrec
def collatz(
n: BigInt,
acc: BigInt,
fn: (BigInt, BigInt) => Unit
): BigInt = {
fn(n, acc)
if (n == 1) {
acc
} else if (n % 2 == 0) {
collatz(n / 2, acc + 1, fn)
} else {
collatz(3 * n + 1, acc + 1, fn)
}
}
这里我使用Collatz Conjecture 计算一个数字达到1 时的计数。举个例子,让我们假设它的号码为32
val n = BigInt("32")
val c = collatz(n, 0, (num, acc) => {
println("Num -> " + num + " " + " " + "Acc -> " + acc)
})
我得到以下输出。
Num -> 32 Acc -> 0
Num -> 16 Acc -> 1
Num -> 8 Acc -> 2
Num -> 4 Acc -> 3
Num -> 2 Acc -> 4
Num -> 1 Acc -> 5
普通递归解决方案将返回每个数字的准确计数。例如编号2 在1 步骤中达到1。因此,每个子问题都有精确的解决方案,但在tailrec 方法中,只有最终结果被正确计算。变量acc 的行为与预期的循环变量完全相同。
如何更改尾调用优化的代码,同时我可以获得每个子问题的准确值。简而言之,我怎样才能获得Stack 变量的acc 行为类型。
另外,一个相关的问题是 lambda 函数 fn 的开销对于 n 的大值会有多大,假设不会使用 println 语句。
我正在添加一个递归解决方案,它可以为子问题产生正确的解决方案。
def collatz2(
n: BigInt,
fn: (BigInt, BigInt) => Unit
): BigInt = {
val c: BigInt = if (n == 1) {
0
} else if (n % 2 == 0) {
collatz2(n / 2, fn) + 1
} else {
collatz2(3 * n + 1, fn) + 1
}
fn(n, c)
c
}
它产生以下输出。
Num -> 1 Acc -> 0
Num -> 2 Acc -> 1
Num -> 4 Acc -> 2
Num -> 8 Acc -> 3
Num -> 16 Acc -> 4
Num -> 32 Acc -> 5
【问题讨论】:
-
它将递归调用变成一个循环。对不起 tldr;一个常见的习惯用法是制作一个本地 def tailrec,其中一些 args,如来自外部 def 的
fn是固定的。然后 outdef 只是用初始值调用 tailrec f。 -
@som-snytt 我知道我们可以使用嵌套方法来使默认参数更好地工作。我的主要问题是关于如何使尾递归解决方案解决类似于递归解决方案的子问题。
-
您首先说您想“找到 [the] annotation
@tailrec的工作原理”,但您的问题完全是关于尾递归代码,而不是注释。@tailrec注解对编译后的代码没有影响。如果指定的例程不是尾递归,它只会发出错误。 -
@jwvh 在正常的递归函数中,我们可以为基本情况返回 1,递归调用返回子问题的结果。添加
@tailrec注解使其仅使用acc变量。 -
@Hariharan,这是不正确的。添加
@tailrec不会改变代码的编译或运行方式。它的唯一目的是在编译时通知开发人员,如果注解的方法是不是尾递归的。如果方法是尾递归的,那么注解什么也不做。如果方法是 not 尾递归的,那么注解只会停止编译。就是这样。
标签: scala recursion tail-recursion tail-call-optimization