This is the parent pointer tree data structure. Something like that may help:
import scala.xml._
case class Category(parentId: Int, id: Int, name: String, order: Ordering[Category])
val rootOrder = Ordering.by[Category, String](_.name) //ordering example
def unflatten(c: List[Category])(implicit rootOrder: Ordering[Category]): NodeSeq = <root>{
unflatten(0, c.groupBy(_.parentId))
}</root>
def unflatten(id: Int, all: Map[Int, List[Category]])(implicit order: Ordering[Category]): NodeSeq =
all.get(id).map(_.sorted.map(c =>
<category id = {c.id.toString} name = {c.name}>{
unflatten(c.id, all)(c.order) // attaching sub-elements
}</category>)).getOrElse(Nil) // or nothing
scala> unflatten(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))(rootOrder)
res11: scala.xml.NodeSeq = <root><category id="1" name="aaa"><category id="2" name="bbb"></category></category><category id="3" name="aaa"></category></root>
Ordering
should be passed as implicit parameter to the sorted
function.
It's not tail recursive, but requires a stack with size equals max nesting levels in your structure - so as you have a whole list of categories in the memory and it fits - stack won't be more than that. If you need something more scalable - it's better to use your DB for unflattening, like CONECT BY in Oracle:
http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm
You can also traverse tree starting from its leafs (it will be tail recursive) using groupBy
, something like:
import scala.annotation._
type Cat = Category
case class Node(cat: Cat, children: List[Node]) {
/** can't be added as implicit because of potential stackoverflow */
val xml: scala.xml.NodeSeq = <category id = {cat.id.toString} name = {cat.name}>{
implicit val sort = cat.order.on[Node](_.cat)
children.sorted.map(_.xml) // attaching sub-elements
}</category>
}
@tailrec def unflatten(leafs: List[Node], acc: Map[Int, Node]) : List[Node] = {
val parents =
for((id, children) <- leafs.groupBy(_.cat.parentId);
p <- acc.get(id))
yield id -> Node(p.cat, (p.children ++ children))
if (parents.nonEmpty) unflatten(parents.values.toList, acc ++ parents) else all.values.toList.filter(_.cat.parentId == 0)
}
def unflatten(cats: List[Cat]): List[Node] = {
val grouped = cats.groupBy(_.id).mapValues(_.map(Node(_, Nil)).head) //Map[Int, Node]
val byParent = cats.groupBy(_.parentId)
val leafs = grouped.filter(x => !byParent.contains(x._1)).values.toList //no children
unflatten(leafs, grouped)
}
def unflattenXml(cats: List[Cat])(implicit ord: Ordering[Node] = rootOrder.on(_.cat)) =
<root>{unflatten(cats).sorted.map(_.xml)}</root>
scala> unflatten(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))
res15: List[Node] =
List(Node(Category(0,1,aaa,scala.math.Ordering$$anon$9@2412c5e9),
List(Node(Category(1,2,bbb,scala.math.Ordering$$anon$9@2412c5e9),List()))),
Node(Category(0,3,aaa,scala.math.Ordering$$anon$9@2412c5e9),List()))
scala> unflattenXml(List(Category(0, 1, "aaa", rootOrder), Category (1,2, "bbb", rootOrder), Category(0, 3, "aaa", rootOrder)))
res29: scala.xml.Elem = <root><category id="1" name="aaa"><category id="2" name="bbb"></category></category><category id="3" name="aaa"></category></root>
If you're looking for sources: