如何使这个Scala函数(“flatMap”变体)尾递归?

时间:2011-12-09 21:56:52

标签: scala recursion tail-recursion

我正在查看以下代码

http://aperiodic.net/phil/scala/s-99/p26.scala

具体地

def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => Nil
      case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f)
    }

我得到大值的StackOverflowError可能是因为函数不是尾递归的。有没有办法转换函数以适应大数?

3 个答案:

答案 0 :(得分:6)

绝对不是尾递归。 f(sublist) :::正在修改递归调用的结果,使其成为一个普通的堆栈递归而不是尾递归。

确保函数是尾递归的一种方法是将@annotation.tailrec放在任何您期望尾递归的函数上。如果无法执行尾调用优化,编译器将报告错误。

为此,我会添加一个实际上是尾递归的小辅助函数:

def flatMapSublistsTR[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = {
  @annotation.tailrec
  def helper(r: List[B], ls: List[A]): List[B] = {
    ls match {
      case Nil => r
      case sublist@(_ :: tail) => helper(r ::: f(sublist), tail)
    }
  }
  helper(Nil, ls)
}

由于我不明显的原因,结果的顺序与原始函数不同。但是,它看起来像是有效的: - )固定。

答案 1 :(得分:3)

以下是实现该功能的另一种方法:

scala> def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] =
     |   List.iterate(ls, ls.size)(_.tail).flatMap(f)
flatMapSublists: [A, B](ls: List[A])(f: List[A] => List[B])List[B]

简单比较dave的flatMapSublistsTR和我的:

scala> def time(count: Int)(call : => Unit):Long = {
     |    val start = System.currentTimeMillis
     |    var cnt =  count
     |    while(cnt > 0) {
     |       cnt -= 1
     |       call
     |    }
     |    System.currentTimeMillis - start
     | }
time: (count: Int)(call: => Unit)Long

scala> val xs = List.range(0,100)

scala> val fn = identity[List[Int]] _
fn: List[Int] => List[Int] = <function1>

scala> time(10000){ flatMapSublists(xs)(fn) }
res1: Long = 5732

scala> time(10000){ flatMapSublistsTR(xs)(fn) }
res2: Long = 347232

将flatMapSublistsTR方法实现为:

def flatMapSublistsTR[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = {
  @annotation.tailrec
  def helper(r: List[B], ls: List[A]): List[B] = {
    ls match {
      case Nil => r
      case sublist@(_ :: tail) => helper(r ::: f(sublist), tail)
    }
  }
  helper(Nil, ls)
}

答案 2 :(得分:1)

def flatMapSublists2[A,B](ls: List[A], result: List[B] = Nil)(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => result
      case sublist@(_ :: tail) => flatMapSublists2(tail, result ++ f(sublist))(f)
    }

您通常只需要添加一个结果结果参数,从一次迭代到下一次迭代,并在结尾处吐出结果,而不是将结尾添加到列表中。

此外,sublist@事情可以简化为

case _ :: tail => flatMapSublists2(tail, result ++ f(ls))(f)

偏离主题:这是我如何解决问题26,而不需要像上面那样的辅助方法。如果你可以使这个尾递归,有一个金星。

  def combinations[A](n: Int, lst: List[A]): List[List[A]] = n match {
    case 1 => lst.map(List(_))
    case _ => lst.flatMap(i => combinations (n - 1, lst.dropWhile(_ != i).tail) map (i :: _))
  }