scaloi.data.ListTree.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scaloi_2.13 Show documentation
Show all versions of scaloi_2.13 Show documentation
Fyne thyngges from Learning Objects, an LO Venture
The newest version!
/*
* Copyright 2007 Learning Objects
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package scaloi
package data
import scalaz._
import scalaz.std.list.listInstance
import scalaz.syntax.std.boolean._
import scalaz.syntax.foldable._
import scala.annotation.nowarn
import scala.collection.{IterableOnce, mutable}
import scala.language.implicitConversions
import scala.util.hashing.MurmurHash3
/** A tree backed by a [[scala.List]].
*
* Isomorphic to `Cofree[List[T]]`, but contains extra tree-based methods.
*
* @param rootLabel The label at the root of this tree.
* @param subForest The child nodes of this tree.
* @see [[scalaz.Cofree]]
*/
case class ListTree[A](
rootLabel: A,
subForest: List[ListTree[A]]
) {
import ListTree._
/**
* Run a bottom-up algorithm.
*
* This is the framework for several stackless methods, such as map.
*
* @param reduce is a function from a label and its mapped children to the new result.
*/
private[data] def runBottomUp[B](reduce: A => mutable.ListBuffer[B] => B): B = {
val root = BottomUpStackElem[A, B](None, this)
var stack = root :: Nil
while (stack.nonEmpty) {
val here = stack.head
if (here.hasNext) {
val child = here.next()
val nextStackElem = BottomUpStackElem[A, B](Some(here), child)
stack = nextStackElem :: stack
} else {
//The "here" node is completed, so add its result to its parents completed children.
val result = reduce(here.rootLabel)(here.mappedSubForest)
here.parent.foreach(_.mappedSubForest += result)
stack = stack.tail
}
}
reduce(root.rootLabel)(root.mappedSubForest)
}
/** Maps the elements of the ListTree into a Monoid and folds the resulting ListTree. */
def foldMap[B: Monoid](f: A => B): B =
foldLeft(Monoid[B].zero)((a, b) => Monoid[B].append(b, f(a)))
def foldLeft[B](z: B)(f: (A, B) => B): B = {
var stack = List(this) :: Nil
var result = z
while (stack.nonEmpty) {
val head :: tail = stack
if (head.isEmpty) {
stack = tail
} else {
val h2 :: t2 = head
result = f(h2.rootLabel, result)
stack = h2.subForest :: t2 :: tail
}
}
result
}
def foldRight[B](z: B)(f: (A, => B) => B): B =
rflatten.foldLeft(z)((a, b) => f(b, a))
/** A 2D String representation of this ListTree. */
def drawTree(implicit sh: Show[A]): String = {
toTree.drawTree
}
/** A histomorphic transform. Each element in the resulting tree
* is a function of the corresponding element in this tree
* and the histomorphic transform of its children.
*/
def scanr[B](g: (A, List[ListTree[B]]) => B): ListTree[B] =
runBottomUp(scanrReducer(g))
/** Pre-order traversal. */
def flatten: List[A] = rflatten.reverse
/** Reverse pre-order traversal. */
def rflatten: List[A] = foldLeft(List.empty[A])(_ :: _)
def size: Int = foldLeft(0)((_, b) => b + 1)
/** Breadth-first traversal. */
def levels: List[List[A]] = {
var level = List(this)
val result = mutable.ListBuffer.empty[List[A]]
while (level.nonEmpty) {
result += level.map(_.rootLabel)
level = level.flatMap(_.subForest)
}
result.toList
}
def toTree: Tree[A] = {
Tree.Node[A](rootLabel, subForest.toEphemeralStream.map(_.toTree))
}
/** Binds the given function across all the subtrees of this tree. */
def cobind[B](f: ListTree[A] => B): ListTree[B] = unfoldTree(this)(t => (f(t), t.subForest))
def foldNode[Z](f: A => List[ListTree[A]] => Z): Z =
f(rootLabel)(subForest)
def map[B](f: A => B): ListTree[B] = {
runBottomUp(mapReducer(f))
}
def flatMap[B](f: A => ListTree[B]): ListTree[B] = {
runBottomUp(flatMapReducer(f))
}
def flatCollect[B](pf: PartialFunction[A, B]): List[B] = {
flatten.collect(pf)
}
def traverse1[G[_] : Apply, B](f: A => G[B]): G[ListTree[B]] = {
val G = Apply[G]
subForest match {
case Nil => G.map(f(rootLabel))(Leaf(_))
case x :: xs => G.apply2(f(rootLabel), NonEmptyList.nel(x, IList.fromList(xs)).traverse1(_.traverse1(f))) {
case (h, t) => Node(h, t.list.toList)
}
}
}
def zip[B](b: ListTree[B]): ListTree[(A, B)] = {
val root = ZipStackElem[A, B](None, this, b)
var stack = root :: Nil
while (stack.nonEmpty) {
val here = stack.head
if (here.hasNext) {
val (childA, childB) = here.next()
val nextStackElem = ZipStackElem[A, B](Some(here), childA, childB)
stack = nextStackElem :: stack
} else {
//The "here" node is completed, so add its result to its parents completed children.
val result = ListTree((here.a.rootLabel, here.b.rootLabel), here.mappedSubForest.toList)
here.parent.foreach(_.mappedSubForest += result)
stack = stack.tail
}
}
ListTree((rootLabel, b.rootLabel), root.mappedSubForest.toList)
}
/**
* This implementation is 24x faster than the trampolined implementation for ListTreeTestJVM's hashCode test.
*
* @return
*/
override def hashCode(): Int =
MurmurHash3.listHash(rflatten, "ListTree".hashCode)
override def equals(obj: scala.Any): Boolean = {
obj match {
case other: ListTree[A] =>
ListTree.badEqInstance[A].equal(this, other)
case _ =>
false
}
}
// from scaloi syntax for other trees
/** A catamorphism over a tree.
*
* `f` is invoked with the label of the root of `self` and with the result
* of folding `self`'s children with `f`.
*
* @note this may stack-overflow on very large trees.
*/
def foldTree[B](f: (A, List[B]) => B): B = {
def loop(current: ListTree[A]): B = {
f(current.rootLabel, current.subForest.map(loop))
}
loop(this)
}
/** A top-down histomorphism over a tree.
*
* The tree is mapped with a function `f` that that can draw from
* the root label of each node and the mapped values of ancestor
* nodes.
*/
def tdhisto[B](f: (List[B], A) => B): ListTree[B] = {
def loop(tree: ListTree[A], ancestors: List[B]): ListTree[B] = {
val b = f(ancestors, tree.rootLabel)
Node(b, tree.subForest.map(loop(_, b :: ancestors)))
}
loop(this, Nil)
}
/** [[tdhisto]], where the resulting element type is `A`.
*
* This is used to work around Scala's local type inference algorithm.
*
* @see [[tdhisto]]
*/
def tdhistendo(f: (List[A], A) => A): ListTree[A] = tdhisto(f)
/** Left-biased tree filter. Errs on the side of exclusivity: If an ancestor
* is excluded then so too will be its descendants.
*
* @param f the predicate
* @return the resulting filtered tree, if any
*/
def filtl(f: A => Boolean): Option[ListTree[A]] = {
def loop(tree: ListTree[A]): Option[ListTree[A]] = tree match {
case Node(content, subForest) =>
f(content) option Node(content, subForest.flatMap(loop))
}
loop(this)
}
/** Right-biased tree filter. Errs on the side of inclusivity: If a descendant
* is included then so too will be its ancestors.
*
* @param f the predicate
* @return the resulting filtered tree, if any
*/
def filtr(f: A => Boolean): Option[ListTree[A]] = {
def loop(tree: ListTree[A]): Option[ListTree[A]] = tree match {
case Node(content, subForest) =>
val filteredForest = subForest.flatMap(loop)
(f(content) || filteredForest.nonEmpty) option Node(content, filteredForest)
}
loop(this)
}
/** Finds matching subtrees within this tree. Will return multiple matches, but
* will not search descendants once it collects a match. I.e. if the root matches,
* then the return will be a list of only the root.
*
* @param f the predicate
* @return the resulting found subtrees, if any
*/
def findSubtrees(f: A => Boolean): List[ListTree[A]] = {
def loop(tree: ListTree[A]): List[ListTree[A]] = tree match {
case node@Node(content, subForest) =>
if (f(content)) {
List(node)
} else {
subForest.flatMap(loop)
}
}
loop(this)
}
/** Rebuild this tree, at each level mapping over the label and the
* to-be-mapped subforest.
*/
def rebuild[B](f: (A, List[ListTree[B]]) => ListTree[B]): ListTree[B] = {
def loop(tree: ListTree[A]): ListTree[B] =
f(tree.rootLabel, tree.subForest.map(loop))
loop(this)
}
/** Rebuild this tree without changing the element type.
*
* Can help with inference.
*/
@inline
def endoRebuild(f: (A, List[ListTree[A]]) => ListTree[A]): ListTree[A] = rebuild(f)
/** Flatten this tree and then flatmap the resulting list with `f` */
def flatterMap[B](f: A => IterableOnce[B]): List[B] =
flatten.flatMap(f)
/** Flatten this tree and then flatten the result. */
def flattern[B](implicit asTraversable: A => IterableOnce[B]): List[B] =
flatterMap(asTraversable)
/** Select the `ix`th subtree of this tree, if it exists. */
def get(ix: Int): Option[ListTree[A]] = subForest.lift.apply(ix)
/** Finds a node matching the given predicate and returns the
* path from the matching node to the root. */
def findPath(f: A => Boolean): Option[List[ListTree[A]]] = {
import scaloi.syntax.foldable._
import syntax.std.boolean._
def find(tree: ListTree[A], parents: List[ListTree[A]]): Option[List[ListTree[A]]] = {
val path = tree :: parents
f(tree.rootLabel) option path orElse tree.subForest.findMap(find(_, path))
}
find(this, Nil)
}
/** Zip the tree's elements with their depth in the tree. */
def zipWithDepth: ListTree[(A, Int)] = {
def loop(node: ListTree[A], depth: Int): ListTree[(A, Int)] = node match {
case Node(content, children) =>
Node((content, depth), children.map(loop(_, 1 + depth)))
}
loop(this, 0)
}
def at(ixen: Int*): Option[A] = {
ixen.foldLeft(Option(this)) {
case (Some(rest), ix) => rest.subForest.lift.apply(ix)
case (None, _ ) => None
}.map(_.rootLabel)
}
}
sealed abstract class ListTreeInstances {
implicit val listTreeInstance
: Traverse1[ListTree] with Monad[ListTree] with Comonad[ListTree] with Align[ListTree] with Zip[ListTree] =
new Traverse1[ListTree] with Monad[ListTree] with Comonad[ListTree] with Align[ListTree] with Zip[ListTree] {
def point[A](a: => A): ListTree[A] = ListTree.Leaf(a)
def cobind[A, B](fa: ListTree[A])(f: ListTree[A] => B): ListTree[B] = fa cobind f
def copoint[A](p: ListTree[A]): A = p.rootLabel
override def map[A, B](fa: ListTree[A])(f: A => B) = fa map f
def bind[A, B](fa: ListTree[A])(f: A => ListTree[B]): ListTree[B] = fa flatMap f
def traverse1Impl[G[_]: Apply, A, B](fa: ListTree[A])(f: A => G[B]): G[ListTree[B]] = fa traverse1 f
override def foldRight[A, B](fa: ListTree[A], z: => B)(f: (A, => B) => B): B = fa.foldRight(z)(f)
override def foldMapRight1[A, B](fa: ListTree[A])(z: A => B)(f: (A, => B) => B) =
(fa.flatten.reverse: @unchecked) match {
case h +: t => t.foldLeft(z(h))((b, a) => f(a, b))
}
override def foldLeft[A, B](fa: ListTree[A], z: B)(f: (B, A) => B): B =
fa.flatten.foldLeft(z)(f)
@nowarn("msg=exhaustive")
override def foldMapLeft1[A, B](fa: ListTree[A])(z: A => B)(f: (B, A) => B): B = fa.flatten match {
case h +: t => t.foldLeft(z(h))(f)
}
override def foldMap[A, B](fa: ListTree[A])(f: A => B)(implicit F: Monoid[B]): B = fa foldMap f
//This implementation is 14x faster than the trampolined implementation for ListTreeTestJVM's align test.
override def alignWith[A, B, C](f: A \&/ B => C): (ListTree[A], ListTree[B]) => ListTree[C] = { (a, b) =>
import ListTree.AlignStackElem
val root = AlignStackElem[A, B, C](None, \&/(a, b))
var stack = root :: Nil
while (stack.nonEmpty) {
val here = stack.head
if (here.hasNext) {
val nextChildren = here.next()
val nextStackElem = AlignStackElem[A, B, C](Some(here), nextChildren)
stack = nextStackElem :: stack
} else {
//The "here" node is completed, so add its result to its parents completed children.
val result = ListTree[C](f(here.trees.bimap(_.rootLabel, _.rootLabel)), here.mappedSubForest.toList)
here.parent.foreach(_.mappedSubForest += result)
stack = stack.tail
}
}
ListTree(f(root.trees.bimap(_.rootLabel, _.rootLabel)), root.mappedSubForest.toList)
}
override def zip[A, B](a: => ListTree[A], b: => ListTree[B]): ListTree[(A, B)] = {
a.zip(b)
}
}
implicit def treeEqual[A](implicit A0: Equal[A]): Equal[ListTree[A]] =
new ListTreeEqual[A] { def A = A0 }
implicit def treeOrder[A](implicit A0: Order[A]): Order[ListTree[A]] =
new Order[ListTree[A]] with ListTreeEqual[A] {
def A = A0
override def order(x: ListTree[A], y: ListTree[A]) =
A.order(x.rootLabel, y.rootLabel) match {
case Ordering.EQ =>
std.list.listOrder[ListTree[A]].order(x.subForest, y.subForest)
case x => x
}
}
}
object ListTree extends ListTreeInstances {
/**
* Node represents a tree node that may have children.
*
* You can use Node for tree construction or pattern matching.
*/
object Node {
def apply[A](root: A, forest: List[ListTree[A]]): ListTree[A] = {
ListTree[A](root, forest)
}
def unapply[A](t: ListTree[A]): Some[(A, List[ListTree[A]])] = Some((t.rootLabel, t.subForest))
}
/**
* Leaf represents a tree node with no children.
*
* You can use Leaf for tree construction or pattern matching.
*/
object Leaf {
def apply[A](root: A): ListTree[A] = {
Node(root, Nil)
}
def unapply[A](t: ListTree[A]): Option[A] = {
t match {
case Node(root, List()) =>
Some(root)
case _ =>
None
}
}
}
def unfoldForest[A, B](s: List[A])(f: A => (B, List[A])): List[ListTree[B]] =
s.map(unfoldTree(_)(f))
def unfoldTree[A, B](v: A)(f: A => (B, List[A])): ListTree[B] =
f(v) match {
case (a, bs) => Node(a, unfoldForest(bs)(f))
}
//Only used for .equals.
@nowarn("msg=cooperative")
private def badEqInstance[A] = new ListTreeEqual[A] {
override def A: Equal[A] = _ equals _
}
/**
* This implementation is 16x faster than the trampolined implementation for ListTreeTestJVM's scanr test.
*/
private def scanrReducer[A, B](
f: (A, List[ListTree[B]]) => B
)(rootLabel: A)(subForest: mutable.ListBuffer[ListTree[B]]): ListTree[B] = {
val subForestList = subForest.toList
ListTree[B](f(rootLabel, subForestList), subForestList)
}
/**
* This implementation is 10x faster than mapTrampoline for ListTreeTestJVM's map test.
*/
private def mapReducer[A, B](
f: A => B
)(rootLabel: A)(subForest: mutable.ListBuffer[ListTree[B]]): ListTree[B] = {
ListTree[B](f(rootLabel), subForest.toList)
}
/**
* This implementation is 9x faster than flatMapTrampoline for ListTreeTestJVM's flatMap test.
*/
private def flatMapReducer[A, B](
f: A => ListTree[B]
)(root: A)(subForest: mutable.ListBuffer[ListTree[B]]): ListTree[B] = {
val ListTree(rootLabel0, subForest0) = f(root)
ListTree(rootLabel0, subForest0 ++ subForest)
}
private final case class BottomUpStackElem[A, B](
parent: Option[BottomUpStackElem[A, B]],
tree: ListTree[A]
) extends Iterator[ListTree[A]] {
private[this] var subPosition = tree.subForest
private[data] def rootLabel = tree.rootLabel
private[data] val mappedSubForest = mutable.ListBuffer.empty[B]
override def hasNext: Boolean = subPosition.nonEmpty
override def next(): ListTree[A] = {
val head = subPosition.head
subPosition = subPosition.tail
head
}
}
private final case class ZipStackElem[A, B](
parent: Option[ZipStackElem[A, B]],
a: ListTree[A],
b: ListTree[B]
) extends Iterator[(ListTree[A], ListTree[B])] {
private[this] var subPosition = STreeZip(a.subForest, b.subForest)
private[data] val mappedSubForest = mutable.ListBuffer.empty[ListTree[(A, B)]]
override def hasNext: Boolean = subPosition.as.nonEmpty && subPosition.bs.nonEmpty
override def next(): (ListTree[A], ListTree[B]) = {
val head = (subPosition.as.head, subPosition.bs.head)
subPosition = STreeZip(subPosition.as.tail, subPosition.bs.tail)
head
}
}
private[data] final case class AlignStackElem[A, B, C](
parent: Option[AlignStackElem[A, B, C]],
trees: \&/[ListTree[A], ListTree[B]]
) extends Iterator[\&/[ListTree[A], ListTree[B]]] {
private[this] var subPosition = STreeZip(
trees.a.map(_.subForest).getOrElse(List.empty),
trees.b.map(_.subForest).getOrElse(List.empty)
)
private[data] val mappedSubForest = mutable.ListBuffer.empty[ListTree[C]]
override def hasNext: Boolean = subPosition.as.nonEmpty || subPosition.bs.nonEmpty
override def next(): \&/[ListTree[A], ListTree[B]] =
subPosition match {
case STreeZip(a :: aTail, b :: bTail) =>
subPosition = STreeZip(aTail, bTail)
\&/.Both(a, b)
case STreeZip(a :: aTail, Nil) =>
subPosition = STreeZip(aTail, Nil)
\&/.This(a)
case STreeZip(Nil, b :: bTail) =>
subPosition = STreeZip(Nil, bTail)
\&/.That(b)
case STreeZip(Nil, Nil) =>
throw new NoSuchElementException("reached iterator end")
}
}
implicit def ToListTreeUnzip[A1, A2](root: ListTree[(A1, A2)]): ListTreeUnzip[A1, A2] =
new ListTreeUnzip[A1, A2](root)
}
private trait ListTreeEqual[A] extends Equal[ListTree[A]] {
def A: Equal[A]
//This implementation is 4.5x faster than the trampolined implementation for ListTreeTestJVM's equal test.
override final def equal(a1: ListTree[A], a2: ListTree[A]): Boolean = {
import ListTree.Node
if (!A.equal(a1.rootLabel, a2.rootLabel))
return false
var stack = STreeZip(a1.subForest, a2.subForest) :: Nil
while (stack.nonEmpty) {
stack match {
case STreeZip(Node(childA1, childrenA1) :: a1Tail, Node(childA2, childrenA2) :: a2Tail) :: tail =>
if (!A.equal(childA1, childA2))
return false
stack = STreeZip(a1Tail, a2Tail) :: STreeZip(childrenA1, childrenA2) :: tail
case STreeZip(Nil, Nil) :: tail =>
stack = tail
case _ =>
return false
}
}
true
}
}
final class ListTreeUnzip[A1, A2](private val root: ListTree[(A1, A2)]) extends AnyVal {
private def unzipCombiner(rootLabel: (A1, A2))(
accumulator: mutable.ListBuffer[(ListTree[A1], ListTree[A2])]): (ListTree[A1], ListTree[A2]) = {
(ListTree(rootLabel._1, accumulator.map(_._1).toList), ListTree(rootLabel._2, accumulator.map(_._2).toList))
}
/** Turns a tree of pairs into a pair of trees. */
def unzip: (ListTree[A1], ListTree[A2]) = {
root.runBottomUp[(ListTree[A1], ListTree[A2])](unzipCombiner)
}
}
private[data] final case class STreeZip[A, B](
as: List[ListTree[A]],
bs: List[ListTree[B]],
)