Scala 如果我知道节点及其父节点,如何构建树?

Scala 如果我知道节点及其父节点,如何构建树?,scala,tree,Scala,Tree,假设我得到了一些节点和它们的直接父节点,比如: case class Mapping(name: String, parents: Seq[String] = Nil) val mappings = Seq( Mapping("aaa"), Mapping("bbb"), Mapping("ccc"), Mapping("ddd", Seq("aaa", "bbb")), Mapping("eee", Seq("ccc")), Mapping("fff", Seq("d

假设我得到了一些节点和它们的直接父节点,比如:

case class Mapping(name: String, parents: Seq[String] = Nil)

val mappings = Seq(
  Mapping("aaa"),
  Mapping("bbb"),
  Mapping("ccc"),
  Mapping("ddd", Seq("aaa", "bbb")),
  Mapping("eee", Seq("ccc")),
  Mapping("fff", Seq("ddd")),
  Mapping("ggg", Seq("aaa", "fff")),
  Mapping("hhh")
)
如何在Scala中编写一个函数,在它们的基础上构建一个树

def buildTrees(data: Seq[Mapping]): Seq[Node] = ???

case class Node(name: String, children: Seq[Node] = Nil)

val trees = buildTrees(mappings)

private val expectedTree = Seq(
  Node("aaa", Seq(
    Node("ggg"),
    Node("ddd", Seq(
      Node("fff", Seq(
        Node("ggg")
      ))))
  )),
  Node("bbb", Seq(
    Node("ddd", Seq(
      Node("fff", Seq(
        Node("ggg")
      ))))
  )),
  Node("ccc", Seq(
    Node("eee")
  )),
  Node("hhh", Seq())
)

if (trees == expectedTree) {
  println("OK")
} else {
  println("Not equal")
}
如何实现
buildTrees
方法?我想了一会儿,但可以得到一个优雅的解决方案


更新:希望看到具有不可变数据的解决方案

def buildTrees(数据:Seq[Mapping]):Seq[Node]={
def attachToParents(newChild:Mapping,parent:Seq[Node]):Seq[Node]={
对于(父accu
案例头部+:尾部=>头部匹配{
案例映射(name,Seq())=>helper(tail,accu:+Node(name))
案例映射(名称、父项)=>helper(tail、attachToParents(head、accu))
}
}
助手(数据,Seq())
}
def buildTrees(数据:Seq[Mapping]):Seq[Node]={
def attachToParents(newChild:Mapping,parent:Seq[Node]):Seq[Node]={
对于(父accu
案例头部+:尾部=>头部匹配{
案例映射(name,Seq())=>helper(tail,accu:+Node(name))
案例映射(名称、父项)=>helper(tail、attachToParents(head、accu))
}
}
助手(数据,Seq())
}

另一个实现是:

  • 有效的
  • 堆栈不溢出
  • 纯函数

与谢飞的解决方案的主要区别在于:

  • 每个节点在其所有子节点都已创建之后,只构建一次 已构建,即无
    copy
    call
  • 检测循环引用
  • 所有发现都是通过高效的
    Map
    Set
    操作实现的

因此,它可能不是最简单的,而是50%的生产准备就绪。

另一个实现是:

  • 有效的
  • 堆栈不溢出
  • 纯函数

与谢飞的解决方案的主要区别在于:

  • 每个节点在其所有子节点都已创建之后,只构建一次 已构建,即无
    copy
    call
  • 检测循环引用
  • 所有发现都是通过高效的
    Map
    Set
    操作实现的


因此,它可能不是最简单的,而是50%的生产就绪。

这看起来很奇怪,因为相同的源节点可以在输出中复制。谢谢,问题已解决。这听起来更像是一个有向无环图(DAG)-在树中,节点有0或1个父节点,而不是多个父节点。@Bergi这是一个有效的问题,因为结果数据绝对是一个树。源数据中的重复项被克隆到不同的分支。听起来好像您没有构建树。映射和expectedTree都没有描述树(每个节点有一个父节点)。这看起来很奇怪,因为相同的源节点可以在输出中复制。谢谢,问题已经解决。这听起来更像是一个有向无环图(DAG)-在树中,节点有0或1个父节点,而不是多个父节点。@Bergi这是一个有效的问题,因为结果数据绝对是一个树。源数据中的重复项被克隆到不同的分支。听起来好像您没有构建树。映射和expectedTree都没有描述树(每个节点有一个父节点).我不得不说代码太复杂了,即使我一步一步地调试它,我也能理解step@Freewind嗯,对不起。我可以试着稍后将其拆分为几个简单的函数,而不是由fold和tailrec组成的大型函数。现在我了解了基本思想。你从叶子开始,逐个处理,将新的小树放到
节点上de>,并从
未解析
中移除叶子,然后将新叶子从
未解析
中放入
队列
,然后重复。非常感谢~@Freewind。感谢你为我解释它所做的工作。我不得不说,代码非常复杂,即使我一步一步地调试它,我也能理解它step@Freewind对不起,我可以试试以后要将其拆分为几个简单的函数,而不是由fold和tailrecNow组成的大型函数,我了解了基本思想。您从叶子开始,逐个处理它们,将新的小树放到
节点
,然后从
未解析
移除叶子,然后将新叶子从
未解析
放到
队列
,然后重复一遍。非常感谢~@Freewind。谢谢你帮我解释
import scala.collection.immutable.Queue

class CyclicReferences(val nodes: Seq[String])
  extends RuntimeException(f"elements withing cycle detected: ${nodes mkString ","}")

def buildTrees(data: Seq[Mapping]): Seq[Node] = {
  val parents = data.map(m => (m.name, m.parents)).toMap withDefaultValue Seq.empty
  val children = data.flatMap(m => m.parents map ((_, m.name))).groupBy(_._1).mapValues(_.map(_._2))

  def loop(queue: Queue[String], unresolved: Map[String, Set[String]], nodes: Map[String, Node]): TraversableOnce[Node] = queue match {
    case Seq() => if (unresolved.isEmpty) nodes.values else throw new CyclicReferences(unresolved.keys.toSeq)
    case key +: rest =>
      val (newQueue, newUnresolved) = ((rest, unresolved) /: parents(key)) { (pair, parent) =>
        val (queue, children) = pair
        val ch = children(parent) - key
        if (ch.isEmpty) (queue :+ parent, children - parent)
        else (queue, children.updated(parent, ch))
      }
      val node = Node(key, children.getOrElse(key, Seq.empty) map nodes)
      loop(newQueue, newUnresolved, nodes + (key -> node))
  }
  val initial = Queue(parents.keys.filter(key => !children.contains(key)).toSeq: _*)
  val unresolved = children mapValues (_.toSet) withDefaultValue Set.empty
  loop(initial, unresolved, Map()).filter(node => parents(node.name).isEmpty).toIndexedSeq
}