scala

时间:2017-08-22 17:40:26

标签: scala tail-recursion

我想理解,这种尾递归将如何在引擎盖下执行,我最近在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>

  • val tmp = firstLangInTag(Scala,(PHP,Scala))=&gt;返回Some(2)
  • val tmp = firstLangInTag(Scala,(Scala))=&gt;返回一些(1)

所以我们有一个答案一些(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中,并在每次下一次迭代时更新它;

提前谢谢!

3 个答案:

答案 0 :(得分:0)

我会让那些对Scala更精通的人在每个执行阶段给出关于堆栈内容的更详细的答案,但是我注意到tmp被设置为函数的返回值。在向前遍历列表时,不存储任何中间值,但每次调用firstLangIntTag都会将函数应用于返回给它的调用结果。这是可能的,因为函数是Scala中的对象。

答案 1 :(得分:0)

E.g val tmp = firstLangInTag(Some("Scala"), List("PHP", "C#", "Scala")) => returns Some(2)

在上面的示例中,您将有三次调用您的函数。每个早些时候返回结果都要等待跟着他执行:

  • 1st firstLangInTag(Some("Scala"), List("PHP", "C#", "Scala"))
  • 第二firstLangInTag(Some("Scala"), List("C#", "Scala"))
  • 3rd 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注释,编译失败。你对于处理tmpi的部分如何工作持怀疑态度是对的,因为这正是防止它被尾递归的部分。