尾递归:在scala中过滤嵌套列表

时间:2018-03-28 10:51:26

标签: scala nested-lists tail-recursion

以下函数在嵌套列表中过滤(删除1')

def filter(list : List[Any], acc : List[Any] = Nil) : List[Any] = {
  list match {
    case Nil => acc
    case (l : List[_]) :: tail =>
      val nested = filter(l)

      if (nested.isEmpty) filter(tail, acc)
      else
        filter(tail, acc :+ nested)

    case 1 :: tail =>     filter(tail, acc)
    case other :: tail => filter(tail, acc :+ other)
  }
}

输入

filter(List(1, 3, 4, List(1, 4 ,5), List(1)))

输出

res0: List[Any] = List(3, 4, List(4, 5))

我的问题是:如何使这个尾递归?我的主要问题是如何处理嵌套列表:val nested = filter(l, Nil)

由于

1 个答案:

答案 0 :(得分:2)

好的,您可以使用免费monad 来解决此问题,例如,在Typelevel Cats lib中可用。

"org.typelevel" %% "cats-free" % "1.0.1"

背后的想法是将递归无法转换为尾递归到另一部分代码,这是可行的

因此,如果您想要了解如何解决此类任务,您需要阅读Runar Oli Bjarnason的this论文(并且可能会看this演示文稿)。之后,您可以尝试使用自己编写的蹦床解决问题(基于纸张和视频):

首先让我们以某种方式重写你的过滤器(这一步不是必需的,我只是在纸上的偶数/奇数例子的印象中,下面我展示了如何根据你的变体做同样的事情):

private def filterList(l: List[Any]): Option[Any] = {
  l.foldLeft(Option.empty[List[Any]]) {
    case (acc, next) if acc.isEmpty =>
      filter1(next).map(_ :: Nil)
    case (acc, next) =>
      acc.map(_ ++ filter1(next))
  }
}

private def filter1(a: Any): Option[Any] = a match {
  case 1 => Option.empty[Any]
  case l: List[Any] => filterList(l)
  case t => Some(t)
}

def filter(l: List[Any]): List[Any] = {
  filterList(l) match {
    case Some(l: List[Any]) => l
    case _ => Nil
  }
}

现在让我们假设Trampoline已经可用(并且可以使用,见下文)并使用trampolines重写:

def filterList(l: List[Any]): Trampoline[Option[Any]] = {
  l.foldLeft[Trampoline[Option[List[Any]]]](Done(Option.empty[List[Any]])) {
    case (acc, next) =>
      acc.flatMap {
        case None => filter1(next).map(_.map(_ :: Nil))
        case v =>
          filter1(next).map (r => v.map(_ ++ r))
      }
  }
}

def filter1(a: Any): Trampoline[Option[Any]] = a match {
  case 1 => Done(Option.empty[Any])
  case l: List[Any] => More(() => filterList(l))
  case t => Done(Some(t))
}

def filter(l: List[Any]): List[Any] = {
  filterList(l).runT match {
    case Some(l: List[Any]) => l
    case _ => Nil
  }
}

Trampoline实施(基于纸张和视频)

sealed trait Trampoline[+A] {
  final def runT: A =
    this match {
      case Done(a) => a
      case More(k) => k().runT
      case t: FlatMap[Any, A] => t match {
        case Done(a) FlatMap f => f(a).runT
        case More(k) FlatMap f => k().flatMap(f).runT
        case FlatMap(xg: FlatMap[Any, A], f) =>
          xg.a.flatMap(a => xg.f(a)).runT
      }
    }

  def map[B](f: A => B): Trampoline[B] =
    flatMap(x => More(() => Done(f(x))))

  def flatMap[B](f: A => Trampoline[B]): Trampoline[B] = this match {
    case FlatMap(a, g: Any) => FlatMap(a, (x: Any) => g(x) flatMap f)
    case x => FlatMap(x, f)
  }
}

case class More[+A](k: () => Trampoline[A])
  extends Trampoline[A]

case class Done[+A](result: A)
  extends Trampoline[A]

case class FlatMap[A, B](a: Trampoline[A], f: A => Trampoline[B])
  extends Trampoline[B]

现在,通过所有这些部分,您可以检查现在您是否正在进行无堆栈递归实现。我用这样的代码进行测试:

def check(i: Int, output: Boolean) {
  val l = (1 to i).foldLeft(List[Any](1, 2, 3)) {
    case (l, _) =>
      List[Any](1, 2, 3, l, List(1))
  }

  val res = filter(l)
  if (output) {
    println(s"Depth: $i, result: " + res)
  }
}

check(3, true)
check(10000, false)

使用StackOverflowError的非蹦床方法失败,并在修改过的方法上成功完成。

使用Cats更新

我尝试使用cats-free lib的相同方法。它也很完美:

import cats.free._
import cats.implicits._

def filterList(l: List[Any]): Trampoline[Option[Any]] = {
  l.foldLeft[Trampoline[Option[List[Any]]]](Trampoline.done(Option.empty[List[Any]])) {
    case (acc, next) =>
      acc.flatMap {
        case None => filter1(next).map(_.map(_ :: Nil))
        case v =>
          filter1(next).map (r => v.map(_ ++ r))
      }
  }.map(_.map(identity[Any]))
}

def filter1(a: Any): Trampoline[Option[Any]] = a match {
  case 1 => Trampoline.done(Option.empty[Any])
  case l: List[Any] => Trampoline.defer(filterList(l))
  case t => Trampoline.done(Some(t))
}

def filter(l: List[Any]): List[Any] = {
  filterList(l).run match {
    case Some(l: List[Any]) => l
    case _ => Nil
  }
}

使用Cats Trampoline和原始代码

我尝试对原始代码执行相同操作,结果如下:

import cats.free._
import cats.implicits._

private def filterInner(list : List[Any], acc : List[Any] = Nil) : Trampoline[List[Any]] = {
  list match {
    case Nil => Trampoline.done(acc)
    case (l : List[_]) :: tail =>
      Trampoline.defer(filterInner(l)).flatMap {
        case Nil => Trampoline.defer(filterInner(tail, acc))
        case nested => Trampoline.defer(filterInner(tail, acc :+ nested))
      }
    case 1 :: tail =>     Trampoline.defer(filterInner(tail, acc))
    case other :: tail => Trampoline.defer(filterInner(tail, acc :+ other))
  }
}
def filter(list : List[Any]): List[Any] = filterInner(list).run

注意,对filterInner的每次递归调用都包含在Trampoline.defer中,这样做是为了消除递归。您可以通过删除case (l : List[_]) :: tail部分中的包装并运行我的测试示例来测试它。