2017-07-17 64 views
1

我指定的特质对我的模型:使用递归函数堆栈 - 在Scala中使用Free Monads Trampoline安全吗?

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

然后,我从记录从DB构建树:

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

此代码工作正常,但使用非尾递归调用。所以,我担心的是它会在大量记录中失败。我发现了一个非常好的article关于通过非尾递归使用Free monads Trampoline。 这看起来像要走的路,但我不能重写我的代码以使其堆栈安全。在本文的例子中,函数中只有一个递归调用,但在我的函数中可能会有很多,用于构建树。有人更有经验的自由monads可以帮助我吗?这甚至有可能吗?

+0

列表的长度不是问题,但树的深度是。你可以改变'build'方法返回'Trampoline [SimpleTree]'而不是'children.map {ch => build(newLs,ch)}'使用'children.traverse {ch => build(newLs,ch) }',但你也可以直接以尾递归方式实现它,通过自下而上(从树叶)构建树。 –

+0

你能显示更多细节吗?当将构建方法的结果更改为Trampoline [SimpleTree],然后children.traverse {ch => build(newLs,ch)}在构建时会出现类型不匹配错误(newLs,ch) – user2975535

+0

请参阅我的答案。 –

回答

1

在阐述我的意见,使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

直接进行尾递归仍然是一个很好的练习。

+0

非常感谢。使用分区的很好的回答和很好的建议 – user2975535

1

你可以重写你的函数作为尾递归,没有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) 
    } 
+0

你的代码有问题。这不会返回一棵树,但Map of Leafs – user2975535

+0

'build'返回所有节点的Map(不仅仅是树叶),'apply'返回一个根的列表。 – Dima

+0

对我来说,建立总是返回LeafImps的地图,并应用刚刚返回的根源 – user2975535