簡體   English   中英

創建遞歸函數堆棧-使用Scala中的免費Monads蹦床安全嗎?

[英]Make recursive function stack - safe with Free Monads Trampoline in Scala?

我為模型指定了特征:

sealed trait TreeStructureModel{
  val parentId: Option[Long]
  val title: String
  val id: Long
}

然后,我根據數據庫中的記錄構建一棵樹:

trait SimpleTree[+TreeStructureModel]{
  val title: String
  val id: Long
}
trait Node[+TreeStructureModel] extends SimpleTree[TreeStructureModel]{
  val inner: List[SimpleTree[TreeStructureModel]]
}
trait Leaf[+TreeStructureModel] extends SimpleTree[TreeStructureModel]

case class NodeImp[T <: TreeStructureModel](title: String, inner: List[SimpleTree[T]], id: Long) extends Node[T]
case class LeafImp[T <: TreeStructureModel](title: String, id: Long) extends Leaf[T]

object SimpleTree{
  def apply[T <: TreeStructureModel](ls: List[T]): List[SimpleTree[T]] = {
    def build(ls: List[T], current: T): SimpleTree[T] = {
      val children = ls.filter{ v => v.parentId.isDefined && v.parentId.get == current.id}
      if(children.isEmpty){
        LeafImp(title = current.title, id = current.id)
      } else {
        val newLs = ls.filterNot{ v => v.parentId.isDefined && v.parentId.get == current.id}
        NodeImp(title = current.title, id = current.id, inner = children.map{ch => build(newLs, ch)})
    }
  }
    val roots = ls.filter{ v => v.parentId.isEmpty}
    val others = ls.filterNot{ v => v.parentId.isEmpty}
    roots.map(build(others, _))
  }
}

該代碼可以正常工作,但使用非尾遞歸調用。 因此,我擔心的是它將在大量記錄上失敗。 我找到了一篇很棒的文章,介紹了如何通過非尾遞歸使用Free monads Trampoline。 這看起來很可行,但是我無法重寫代碼以確保其安全。 在本文的示例中,函數中只有一個遞歸調用,但是在我的函數中,可以有很多構建樹的方法。 可以對Free monad更有經驗的人幫助我嗎? 這有可能嗎?

您可以將函數重寫為尾遞歸,而無需使用scalaz。 奧卡姆剃須刀,你知道...

 def build(
   ls: List[T], 
   kids: Map[Long, List[T]],
   result: Map[Long, SimpleTree[T]]
 ) = ls match {
   case Nil => result
   case head :: tail if result.contains(head.id) => build(tail, kids, result)
   case head :: tail =>          
     kids(head.id).partition(result.contains(_.id)) match {
       case (Nil, Nil) => 
         build(tail, kids, result + (head.id->LeafImp(head.title, head.id)))
       case (done, Nil) => 
         build(
           tail,
           kids, 
           result + 
           (head.id->NodeImp(head.title, head.id, done.map(_.id).map(result)))
         )
       case (_, missing) =>
         build(missing ++ tail, kids, result)
     }
  }

  def apply(ls: List[T]) = {
    val (roots, others) = list.partition(_.parentId.isEmpty)
    val nodes = build(ls, others.groupBy(_.parentId.get), Map.empty)
    roots.map(_.id).map(nodes)
  }

詳細說明一下,使build方法返回一個Trampoline並使用traverse代替map

import scalaz.Free.Trampoline
import scalaz.Trampoline
import scalaz.syntax.traverse._
import scalaz.std.list._

sealed trait TreeStructureModel{
  val parentId: Option[Long]
  val title: String
  val id: Long
}

trait SimpleTree[+TreeStructureModel]{
  val title: String
  val id: Long
}
trait Node[+TreeStructureModel] extends SimpleTree[TreeStructureModel]{
  val inner: List[SimpleTree[TreeStructureModel]]
}
trait Leaf[+TreeStructureModel] extends SimpleTree[TreeStructureModel]

case class NodeImp[T <: TreeStructureModel](title: String, inner: List[SimpleTree[T]], id: Long) extends Node[T]
case class LeafImp[T <: TreeStructureModel](title: String, id: Long) extends Leaf[T]

object SimpleTree{
  def apply[T <: TreeStructureModel](ls: List[T]): List[SimpleTree[T]] = {
    def build(ls: List[T], current: T): Trampoline[SimpleTree[T]] = {
      val children = ls.filter{ v => v.parentId.isDefined && v.parentId.get == current.id}
      if(children.isEmpty){
        Trampoline.done(LeafImp(title = current.title, id = current.id))
      } else {
        val newLs = ls.filterNot{ v => v.parentId.isDefined && v.parentId.get == current.id}
        children.traverse(build(newLs, _)).map(trees => NodeImp(title = current.title, id = current.id, inner = trees))
      }
    }
    val roots = ls.filter{ v => v.parentId.isEmpty}
    val others = ls.filterNot{ v => v.parentId.isEmpty}
    roots.map(build(others, _).run)
  }
}

請注意,我對使用Trampoline的代碼進行了最少的必要更改。 我將進一步建議使用一個partition調用,而不是使用一對filterfilterNot

直接使該方法尾部遞歸仍然是一個很好的練習。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM