递归处理关系列表

时间:2016-06-03 22:50:20

标签: scala recursion relation

给定以下数据模型,其中元素通过分隔的字符串路径记录它们与祖先的关系:

case class Entity(id:String, ancestorPath:Option[String] = None)

val a = Entity("a")
val c = Entity("c")
val b = Entity("b", Some("a"))
val d = Entity("d", Some("a/b"))

val test = a :: c :: b :: d :: Nil

将关系处理成嵌套结构的好方法是什么,例如:

case class Entity2(id:String, children:List[Entity])

所需的功能将输出嵌套其子项的Entity2列表。因此,元素本身将是根节点。我们可以假设输入列表以parentId的值进行词典排序,并且None被认为早于列表,正如test一样。

示例所需输出:

List(
  Entity2("a", List(
    Entity2("b", List(
      Entity2("d", Nil)
    ),
  ),
  Entity2("c", Nil)
)

我已经尝试了几次,但是让我感到震惊的是找到一种很好的方法来反转这种关系......递归Entity类会让你向后(我的父/祖先是/是)引用,而所需的输出记录前进(我的孩子是)参考。谢谢!

2 个答案:

答案 0 :(得分:1)

一个直接的解决方案如下:

case class Entity(id:String, ancestorPath:Option[String] = None)
case class Entity2(id:String, children:List[Entity2])

object Main {

  def main(args: Array[String]) {
    val a = Entity("a")
    val c = Entity("c")
    val b = Entity("b", Some("a"))
    val d = Entity("d", Some("a/b"))

    val test = a :: c :: b :: d :: Nil

    println(buildTree(test))
  }

  def immediateParent(path: String) = {
    val pos = path.lastIndexOf('/')
    if (pos == -1) path
    else path.substring(pos+1)
  }

  def buildTree(all: List[Entity]): List[Entity2] = {
    val childEntitiesByParentId = all.groupBy(_.ancestorPath.map(immediateParent _))
    val roots = childEntitiesByParentId.getOrElse(None, Nil)

    roots.map({ root => buildTreeHelper(root, childEntitiesByParentId) })
  }

  def buildTreeHelper(
    parent: Entity,
    childEntitiesByParentId: Map[Option[String], List[Entity]]): Entity2 = {

    val children = childEntitiesByParentId.getOrElse(Some(parent.id), Nil).map({ child =>
      buildTreeHelper(child, childEntitiesByParentId)
    })
    Entity2(parent.id, children)
  }
}

如果你的树很深,你就会砸到堆 - 蹦床是一个很好的解决方案:

import scala.util.control.TailCalls

def buildTree(all: List[Entity]): List[Entity2] = {
  val childEntitiesByParentId = all.groupBy(_.ancestorPath.map(immediateParent _))
  val roots = childEntitiesByParentId.getOrElse(None, Nil)

  buildTreeHelper(roots, childEntitiesByParentId).result
}

def buildTreeHelper(
  parents: List[Entity], 
  childEntitiesByParentId: Map[Option[String], List[Entity]]): TailCalls.TailRec[List[Entity2]] = {

  parents match {
    case Nil => TailCalls.done(Nil)
    case parent :: tail =>
      val childEntities = childEntitiesByParentId.getOrElse(Some(parent.id), Nil)
      for {
        children <- TailCalls.tailcall(buildTreeHelper(childEntities, childEntitiesByParentId))
        siblings <- buildTreeHelper(tail, childEntitiesByParentId)
      } yield Entity2(parent.id, children) :: siblings
  }
}

答案 1 :(得分:0)

从一个空列表开始,然后通过一次添加一个实体来逐步构建它。每次添加实体时,都必须检查其祖先路径,然后遍历新列表中的相应路径以将实体插入正确的位置。目标列表实际上是树结构,因为您有嵌套组件;你只需要在树中找到要插入的正确位置。

如果使用地图而不是列表,效率会更高,但无论哪种方式都可以。如果使用可变结构,您可能会发现构建结果更容易,但也应该是可能的。