【问题标题】:How can I make this Scala function (a "flatMap" variant) tail recursive?如何使这个 Scala 函数(“flatMap”变体)尾递归?
【发布时间】:2011-12-10 15:01:11
【问题描述】:

我正在看下面的代码

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 大概是因为该函数不是尾递归的。有没有办法转换函数以适应大数字?

【问题讨论】:

    标签: scala recursion tail-recursion


    【解决方案1】:

    绝对不是尾递归。 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)
    }
    

    由于对我来说不是很明显的原因,结果的顺序与原始函数不同。但是,它看起来有效:-)已修复。

    【讨论】:

    • 您的结果以错误的顺序出现,因为在原始结果中,您获取当前结果并附加所有剩余结果。在您的 TR 版本中,您的列表 r 正在执行所有以前的结果,因此您需要将当前结果附加到该列表中。
    【解决方案2】:

    这是实现该功能的另一种方式:

    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)
    }
    

    【讨论】:

      【解决方案3】:
      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)
          }
      

      一般只需要添加一个result result参数从一个迭代携带到下一个迭代,并在最后吐出结果而不是将end添加到列表中。

      另外,令人困惑的sublist@ 事情可以简化为

      case _ :: tail =&gt; 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 :: _))
        }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-14
        • 1970-01-01
        相关资源
        最近更新 更多