単純なnode.id、node.parentIdの関連付けが与えられた場合、treeNodeのいくつかのコレクションの左右のノード値を計算する関数があります。それは非常にシンプルで十分に機能します...しかし、まあ、もっと慣用的なアプローチがあるかどうか疑問に思っています。具体的には、外部で追跡された値を使用せずに左/右の値を追跡しながら、おいしい再帰を維持する方法があります。
/*
* A tree node
*/
case class TreeNode(val id:String, val parentId: String){
var left: Int = 0
var right: Int = 0
}
/*
* a method to compute the left/right node values
*/
def walktree(node: TreeNode) = {
/*
* increment state for the inner function
*/
var c = 0
/*
* A method to set the increment state
*/
def increment = { c+=1; c } // poo
/*
* the tasty inner method
* treeNodes is a List[TreeNode]
*/
def walk(node: TreeNode): Unit = {
node.left = increment
/*
* recurse on all direct descendants
*/
treeNodes filter( _.parentId == node.id) foreach (walk(_))
node.right = increment
}
walk(node)
}
walktree(someRootNode)
編集-ノードのリストはデータベースから取得されます。ノードを適切なツリーにプルするには、時間がかかりすぎます。私はフラットリストをメモリにプルしています。私が持っているのは、親と子に関連するノードIDを介した関連付けだけです。
左/右ノード値を追加すると、単一のSQLクエリですべての子(および子の子)のスナップショップを取得できます。
親子の関連付けが変更された場合にデータの整合性を維持するには、計算を非常に迅速に実行する必要があります(これは非常に頻繁に行われます)。
素晴らしいScalaコレクションを使用することに加えて、ツリーノードでの事前/事後フィルタリングに並列処理を使用することで速度を向上させました。左/右ノード値を追跡するより慣用的な方法を見つけたかったのです。@dhgからの回答を見た後、それはさらに良くなりました。フィルタの代わりにgroupByを使用すると、アルゴリズムが4次ではなく線形になります(ほとんどの場合?)。
val treeNodeMap = treeNodes.groupBy(_.parentId).withDefaultValue(Nil)
def walktree(node: TreeNode) = {
def walk(node: TreeNode, counter: Int): Int = {
node.left = counter
node.right =
treeNodeMap(node.id)
.foldLeft(counter+1) {
(result, curnode) => walk(curnode, result) + 1
}
node.right
}
walk(node,1)
}