2015-05-19 70 views
0

說我已經得到了一些節點和他們的父母直接,如:如果我知道節點及其父母,如何構建樹?

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方法?我想了一會兒,但可以得到一個優雅的解決方案。


更新:希望看到一成不變的數據

+0

這看起來很奇怪,因爲相同的源節點可以在輸出中複製。 – user2864740

+0

謝謝,修正問題 – Freewind

+1

這聽起來更像是一個有向無環圖(DAG) - 在一棵樹中,一個節點有0或1個父母,而不是多個父母。 – Bergi

回答

1
def buildTrees(data: Seq[Mapping]): Seq[Node] = { 
    def attachToParents(newChild: Mapping, parents: Seq[Node]): Seq[Node] = { 
    for (parent <- parents) yield { 
     val attachedChildren = attachToParents(newChild, parent.children) 
     if (newChild.parents.contains(parent.name)) 
     parent.copy(children = Node(newChild.name) +: attachedChildren) 
     else 
     parent.copy(children = attachedChildren) 
    } 
    } 

    @tailrec 
    def helper(xs: Seq[Mapping], accu: Seq[Node]): Seq[Node] = xs match { 
    case Seq() => accu 
    case head +: tail => head match { 
     case Mapping(name, Seq()) => helper(tail, accu :+ Node(name)) 
     case Mapping(name, parents) => helper(tail, attachToParents(head, accu)) 
    } 
    } 
    helper(data, Seq()) 
} 
3

解決又一實現其功能是:

  • 高效
  • 棧 - 不溢出
  • 純函數

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 
} 

與瀉肺的解決方案主要區別是:

  • 每個節點建設只有一次,他所有的孩子已經 後已建成,即沒有copy呼叫
  • 檢測循環引用
  • 所有發現均通過高效MapSet操作實施

所以它可能不是最簡單但50%的生產準備。

+0

我不得不說,代碼太複雜了,即使我一步步調試它,我也能理解它。 – Freewind

+0

@Freewind很好,很抱歉。我可以嘗試將它分成幾個簡單的函數,而不是大的摺疊和尾部合成 – Odomontois

+2

現在我明白了基本的想法。你從樹葉開始,逐個處理它們,把新的小樹放到'節點'上,並從'unresolved'中刪除葉子,然後把新葉子從'unresolved'放到'queue',然後重複它。謝謝〜 – Freewind

相關問題