我想理解,这种尾递归将如何在引擎盖下执行,我最近在scala编写的一个大数据赋值中看到了。我还提供了另外两个版本的实现,我宁愿编写这个代码 - 其中一个也是尾递归,另一个是 - 不是;但是,我想了解,第一个实现如何提供相同的结果。
所以,这里是(目的是在列表(ls)中找到特定名称(标签)的索引):
def firstLangInTag(tag: Option[String], ls: List[String]): Option[Int] = {
if (tag.isEmpty) None
else if (ls.isEmpty) None
else if (tag.get == ls.head) Some(0)
else {
val tmp = firstLangInTag(tag, ls.tail)
tmp match {
case None => None
case Some(i) => Some(i + 1)
}
}
}
当我们考虑执行时,参数'tag'定义为Option(“Scala”),'ls'定义为List(“Java”,“PHP”,“Scala”):< / p>
所以我们有一个答案一些(2)并且它是正确的,但有人可以解释一下,执行期间保存的var'i'(隐含的var'tmp')在哪里。是因为,tail-recursion为每次递归执行提供了一个堆栈,而'i'只是保存在内存中,并且每次迭代时都会更新?为什么var'tmp'不会被每次迭代覆盖,而是结果将累积(+ 1)。如果您使用'accumulator'查看以下实现,但再次递归,那么很明显,结果已存储在名为'acc'的变量中,因此'acc'返回此函数的结果:
def firstLangInTag(tag: Option[String], ls: List[String]): Option[Int] = {
def counter(acc: Int, tag: Option[String], ls: List[String]): Int = {
if (tag.isEmpty) acc
else if (ls.isEmpty) acc
else if (tag.get == ls.head) acc
else counter(acc + 1, tag, ls.tail)
}
Option(counter(0, tag, ls))
同样,我们也可以实现与尾递归函数相同的结果(假设它将返回Int而不是Option):
...
else 1 + firstLangInTag(tag, ls.tail);
但是,拜托,有人可以向我解释第一个函数以及scala如何将结果存储在VAL中,并在每次下一次迭代时更新它;
提前谢谢!
答案 0 :(得分:0)
我会让那些对Scala更精通的人在每个执行阶段给出关于堆栈内容的更详细的答案,但是我注意到tmp
被设置为函数的返回值。在向前遍历列表时,不存储任何中间值,但每次调用firstLangIntTag都会将函数应用于返回给它的调用结果。这是可能的,因为函数是Scala中的对象。
答案 1 :(得分:0)
E.g val tmp = firstLangInTag(Some("Scala"), List("PHP", "C#", "Scala")) => returns Some(2)
在上面的示例中,您将有三次调用您的函数。每个早些时候返回结果都要等待跟着他执行:
firstLangInTag(Some("Scala"), List("PHP", "C#", "Scala"))
firstLangInTag(Some("Scala"), List("C#", "Scala"))
firstLangInTag(Some("Scala"), List("Scala"))
最后一次通话将返回Some(0)
,该通话将传播到第二次通话,其中tmp
将被解析为Some(0)
并返回到第一个通话Some(1)
。
最后,第一个电话tmp
将被解析为Some(1)
然后匹配,函数将返回Some(2)
。
第一个解决方案不是尾递归,因为函数的第一次调用需要等待函数调用(带有列表的尾部)找到标记或者在初始列表中缺少标记的情况下返回None。
使用带累加器的版本并添加@tailrec
注释
当您尝试编写尾递归函数时,您总是可以添加@tailrec
注释,以便您可以确定函数是否未正确实现,您将收到错误消息。
答案 2 :(得分:0)
第一个给定的函数不是尾递归的。向其添加@scala.annotation.tailrec
注释,编译失败。你对于处理tmp
和i
的部分如何工作持怀疑态度是对的,因为这正是防止它被尾递归的部分。