Fix #266 by stop recursively traversing a tree

This commit is contained in:
Kazuyoshi Kato 2016-11-02 18:38:55 -07:00
parent 23bbfcc552
commit ffa33961f0
2 changed files with 72 additions and 10 deletions

View File

@ -1,29 +1,56 @@
package coursier.util
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
object Tree {
def apply[T](roots: IndexedSeq[T])(children: T => Seq[T], print: T => String): String = {
/**
* Add elements to the stack
* @param stack a mutable stack which will have elements
* @param elems elements to add
* @param isLast a list that contains whether an element is the last in its siblings or not.
* The first element is the deepest, due to the fact prepending a List is faster than appending
*/
def push(stack: mutable.Stack[(T, List[Boolean])], elems: Seq[T], isLast: List[Boolean]) = {
// Reverse the list because the stack is LIFO but elems must be shown in the order
for ((x, idx) <- elems.zipWithIndex.reverse) {
stack.push((x, (idx == elems.length - 1) :: isLast))
}
}
def helper(elems: Seq[T], prefix: String, acc: String => Unit): Unit =
for ((elem, idx) <- elems.zipWithIndex) {
val isLast = idx == elems.length - 1
def prefix(isLast: List[Boolean]): String = {
// Reverse the list because its first element is the deepest element
isLast.reverse.zipWithIndex.map {
case (last, idx) =>
if (idx == isLast.length - 1)
if (last) "└─ " else "├─ "
else
if (last) " " else "| "
}.mkString("")
}
val tee = if (isLast) "└─ " else "├─ "
// Depth-first traverse
def helper(elems: Seq[T], acc: String => Unit): Unit = {
val stack = new mutable.Stack[(T, List[Boolean])]()
val seen = new mutable.HashSet[T]()
acc(prefix + tee + print(elem))
push(stack, elems, List[Boolean]())
val children0 = children(elem)
while (stack.nonEmpty) {
val (elem, isLast) = stack.pop()
acc(prefix(isLast) + print(elem))
if (children0.nonEmpty) {
val extraPrefix = if (isLast) " " else "| "
helper(children0, prefix + extraPrefix, acc)
if (! seen.contains(elem)) {
push(stack, children(elem), isLast)
seen.add(elem)
}
}
}
val b = new ArrayBuffer[String]
helper(roots, "", b += _)
helper(roots, b += _)
b.mkString("\n")
}

View File

@ -0,0 +1,35 @@
package coursier
package test
import coursier.util.{Tree, Xml}
import utest._
object TreeTests extends TestSuite {
private def tree(str: String, xs: Array[Xml.Node] = Array()): Xml.Node = {
new Xml.Node {
override def label: String = str
override def children = xs
override def attributes: Seq[(String, String, String)] = Array[(String, String, String)]()
override def isText: Boolean = false
override def textContent: String = ""
override def isElement: Boolean = true
}
}
val roots = Array(
tree("p1", Array(tree("c1"), tree("c2"))),
tree("p2", Array(tree("c3"), tree("c4"))))
val tests = TestSuite {
'apply {
val str = Tree(roots)(_.children, _.label)
assert(str == """├─ p1
|| ├─ c1
|| └─ c2
|└─ p2
| ├─ c3
| └─ c4""".stripMargin)
}
}
}