All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalax.collection.GraphTraversal.scala Maven / Gradle / Ivy

The newest version!
package scalax.collection

import scala.annotation.tailrec
import scala.collection.{AbstractIterable, AbstractIterator, EqSetFacade, IndexedSeq}
import scala.collection.mutable.{ArrayBuffer, Builder}
import scala.language.implicitConversions
import scala.math.{max, min}

import scalax.collection.generic.Edge
import scalax.collection.mutable.{EqHashMap, EqHashSet}
import scalax.collection.generic.GenericGraphCoreFactory

/** Graph-related functionality such as traversals, path finding, cycle detection etc.
  *  All algorithms including breadth-first, depth-first, white-gray-black search and
  *  Dijkstra's algorithm are tail recursive.
  *
  * Before starting a traversal a `Traverser` such as [[scalax.collection.GraphTraversal#InnerNodeTraverser]]
  * is instantiated explicitly or implicitly. It holds settings like `maxDepth`, `subgraph` or `ordering`
  * providing a fine-grained control of the traversal. `Traverser`s also extend
  * `scala.collection.Iterable` meaning that you can process the visited nodes and edges
  * in a functional way.
  *
  * @see [[http://www.scala-graph.org/guides/core-traversing]]
  *
  * @define INTOACC taking optional properties like
  *         subgraph restriction, ordering or maximum depth into account.
  * @define EXTNODEVISITOR Alternatively, an instance of `ExtendedNodeVisitor`
  *         may be passed to obtain additional state information such as the current
  *         depth. The concrete type of the last argument, the informer
  *         depends on the underlying implementation so you need to match against it.
  * @define ORD If a `NodeOrdering` or `EdgeOrdering` different from `NoOrdering` is supplied
  *         neighbor nodes will visited during the traversal according to this ordering.
  * @define MAXWEIGHT An optional maximum weight that limits the scope of the traversal or search.
  *         If defined and the sum of edge weights between the root of the traversal and a node
  *         exceeds the given maximum, that node will no more be visited.
  * @define CONSIDERING considering all traversal properties passed to the traverser
  *         factory method like [[scalax.collection.GraphTraversal#innerNodeTraverser]]
  *         or altered by any `with*` method.
  * @define OPTVISITOR An optional function that is applied for its side-effect to
  *         every element visited during graph traversal.
  * @define EXTENDSTYPE which extends `scala.collection.Iterable` with elements of type
  * @define SETROOT and sets its `root` to this node
  * @define TOSTART To start a traversal call one of the graph traversal methods or
  *         any appropriate method inherited from [[scala.collection.Iterable]] on this
  *         instance.
  * @define ROOT The node where subsequent graph traversals start.
  * @define PARAMETERS The properties controlling subsequent traversals.
  * @define SUBGRAPHNODES Restricts subsequent graph traversals to visit only nodes
  *         holding this predicate.
  * @define SUBGRAPHEDGES Restricts subsequent graph traversals to walk only along edges
  *         that hold this predicate.
  * @define DOWNUPBOOLEAN where the `Boolean` parameter is `true` if the traversal takes
  *         place in downward and `false` if it takes place in upward direction.
  * @define PATHSYNTAX `::= ''node'' { ''edge'' ''node'' }`
  * @define WALKPATH A walk/path contains at least one node followed by any number of
  *         consecutive pairs of an edge and a node.
  *         The first element is the start node, the second is an edge with its source
  *         being the start node and its target being the third element etc.
  * @define SANECHECK This optional check is sane if there is reasonable doubt
  *         about the correctness of some algorithm results.
  * @define BUILDERADDS Nodes and edges may be added either alternating or node by node
  *         respectively edge by edge. Either way, the builder ensures that the added
  *         elements build a valid
  * @define BUILDERREC It is recommended using `add` instead of `+=` to track failed
  *         additions.
  * @define EDGESELECTOR Determines the edge to be selected between neighbor nodes
  *         if an edge is not supplied explicitly. This is only relevant in case of
  *         multigraphs.
  * @define SEEFLUENT See `componentTraverser` for more control by means of `FluentProperties`.
  * @define SORTVISITOR called for each inner node or inner edge visited during the sort.
  * @define HOOKSIGNORED Hooks are ignored.
  */
trait GraphTraversal[N, E <: Edge[N]] extends GraphBase[N, E, GraphTraversal] {
  thisGraph =>

  import GraphTraversal._
  import Visitor._

  /** Whether `this` graph is connected if it is undirected or
    *  weakly connected if it is directed.
    */
  def isConnected: Boolean = nodes.headOption forall {
    _.innerNodeTraverser(Parameters(kind = DepthFirst, direction = AnyConnected)).size == nodes.size
  }

  /** Whether `this` graph has at least one cycle in any of its components.
    */
  @inline final def isCyclic: Boolean = findCycle().isDefined

  /** Whether `this` graph has no cycle.
    */
  @inline final def isAcyclic: Boolean = !isCyclic

  /** Finds a cycle in `this` graph in any of its components
    *  and calls `visitor` for each inner element visited during the search.
    *  $SEEFLUENT
    */
  final def findCycle[U](implicit visitor: InnerElem => U = empty): Option[Cycle] =
    componentTraverser().findCycle(visitor)

  /** Finds a cycle that contains `node`
    *  and calls `visitor` for each inner element visited during the search.
    */
  final def findCycleContaining[U](node: NodeT)(implicit visitor: InnerElem => U = empty): Option[Cycle] =
    innerNodeTraverser(node).partOfCycle()

  /** Represents a topological sort layer. */
  case class Layer protected[collection] (index: Int, _nodes: IndexedSeq[NodeT]) {
    def nodes(implicit ordering: NodeOrdering): IndexedSeq[NodeT] =
      if (ordering.isDefined) _nodes sorted ordering
      else _nodes

    def size: Int = _nodes.size
  }

  /** The result of a topological sort in the layered view. */
  type Layers = Iterable[Layer]

  /** Topologically ordered nodes or layers of a topological order of a graph or of an isolated graph component.
    *  @tparam A one of `NodeT`, `N`
    *  @tparam T one of `A` or `(Int, Iterable[A])`
    *  @define NEWFLAVOR Creates a new flavor of this `TopologicalOrder` or `LayeredTopologicalOrder`
    */
  sealed abstract class AbstractTopologicalOrder[+A, +T] extends AbstractIterable[T] {

    protected val layers: Layers
    protected def toA: NodeT => A

    def layerOrdering: NodeOrdering

    /** The number of layers of this topological order. */
    def nrOfLayers = layers.size

    /** $NEWFLAVOR with nodes ordered by `newOrdering` within the layers.
      *  A layer ordering is also useful to ensure a stable topological order over graph instances.
      */
    def withLayerOrdering(newOrdering: NodeOrdering): AbstractTopologicalOrder[A, T]

    /** $NEWFLAVOR that is traversable for its inner nodes zipped with their layers. */
    def toLayered: LayeredTopologicalOrder[A]

    override def hashCode: Int = layers.##

    override def equals(other: Any): Boolean = other match {
      case that: AbstractTopologicalOrder[_, _] =>
        (this.layers eq that.layers) ||
        this.layers == that.layers
      case _ => false
    }

    final override protected def className = getClass.getSimpleName
  }

  /** A traversable topological order of nodes of a graph or of an isolated graph component.
    *  @tparam A one of `NodeT`, `N`
    */
  final class TopologicalOrder[+A] protected[collection] (
      override protected val layers: Layers,
      override protected val toA: NodeT => A
  )(implicit override val layerOrdering: NodeOrdering = NodeOrdering.None)
      extends AbstractTopologicalOrder[A, A] {

    override def iterator: Iterator[A] =
      layers.view.flatMap(layer => layer.nodes).map(toA).iterator // 'view' with respect to Scala 2.12

    def withLayerOrdering(newOrdering: NodeOrdering): TopologicalOrder[A] =
      new TopologicalOrder(layers, toA)(newOrdering)

    def toOuter: TopologicalOrder[N] =
      new TopologicalOrder(layers, _.outer)(layerOrdering)

    def toLayered: LayeredTopologicalOrder[A] =
      new LayeredTopologicalOrder[A](layers, toA)(layerOrdering)

    def nodeCount: Int = layers.foldLeft(0) { case (acc, layer) => acc + layer.size }
  }

  /** Layers of a topological order of a graph or of an isolated graph component.
    *  The layers of a topological sort can roughly be defined as follows:
    *      a. layer 0 contains all nodes having no predecessors,
    *      a. layer n contains those nodes that have only predecessors in ancestor layers
    *         with at least one of them contained in layer n - 1
    *  @tparam A one of `NodeT`, `N`
    */
  final class LayeredTopologicalOrder[+A] protected[collection] (
      override protected val layers: Layers,
      override protected val toA: NodeT => A
  )(implicit override val layerOrdering: NodeOrdering = NodeOrdering.None)
      extends AbstractTopologicalOrder[A, (Int, Iterable[A])] {

    override def iterator: Iterator[(Int, Iterable[A])] =
      layers.view.map(layer => layer.index -> layer.nodes.map(toA)).iterator // 'view' with respect to Scala 2.12

    def withLayerOrdering(newOrdering: NodeOrdering): LayeredTopologicalOrder[A] =
      new LayeredTopologicalOrder(layers, toA)(newOrdering)

    def toOuter: LayeredTopologicalOrder[N] =
      new LayeredTopologicalOrder(layers, _.outer)(layerOrdering)

    def toLayered: LayeredTopologicalOrder[A] = this
  }

  /** Failure result of a topological sort with a possible hint of candidate cycle nodes.
    *
    * @param candidateCycleNodes
    * - If the set is not empty, at least one of the candidate nodes lies on a cycle.
    * - An empty set is only returned if, in any of the components, no node without a predecessor exists
    *   so the algorithm was stopped before any cycle node could have been determined.
    */
  case class TopologicalSortFailure private[collection] (candidateCycleNodes: Set[NodeT]) {

    /** Finds a cycle based on `candidateCycleNodes`.
      * Should always return `Some`. `None` would indicate an internal issue related to the preceding topological sort.
      */
    def cycle: Option[Cycle] =
      if (candidateCycleNodes.isEmpty) findCycle
      else {
        val it = candidateCycleNodes.iterator
        @tailrec def loop: Option[Cycle] =
          if (it.hasNext)
            it.next().findCycle match {
              case None => loop
              case some => some
            }
          else None

        loop
      }
  }

  type TopologicalSort = Either[TopologicalSortFailure, TopologicalOrder[NodeT]]

  /** Sorts this graph topologically.
    * $HOOKSIGNORED
    *  @param visitor $SORTVISITOR
    *  $SEEFLUENT
    */
  final def topologicalSort[U](implicit visitor: InnerElem => U = empty): TopologicalSort =
    componentTraverser().topologicalSort(visitor)

  /** Sorts every isolated component of this graph topologically.
    * $HOOKSIGNORED
    *  @param visitor $SORTVISITOR
    *  $SEEFLUENT
    */
  final def topologicalSortByComponent[U](implicit
      visitor: InnerElem => U = empty
  ): Iterable[TopologicalSort] =
    componentTraverser().topologicalSortByComponent(visitor)

  @inline final protected def defaultPathSize: Int = min(256, nodes.size * 2)

  /** Represents a walk in this graph where `walk` $PATHSYNTAX
    * $WALKPATH
    * @define CUMWEIGHT The cumulated weight of all edges on this path/walk.
    */
  trait Walk extends Iterable[InnerElem] {
    override protected def className = "Walk"

    /** All nodes on this path/walk in proper order. */
    def nodes: Iterable[NodeT]

    /** All edges of this path/walk in proper order. */
    def edges: Iterable[EdgeT]

    /** $CUMWEIGHT */
    final def weight: Double = edges.foldLeft(0d)((sum, edge) => sum + edge.weight)

    /** $CUMWEIGHT
      *
      * @param f The weight function overriding edge weights.
      */
    final def weight[T: Numeric](f: EdgeT => T): T = {
      val num = implicitly[Numeric[T]]
      import num._
      edges.foldLeft(num.zero)((sum, edge) => sum + f(edge))
    }

    /** The number of edges on this path/walk. */
    def length: Int = nodes.size - 1

    /** The number of nodes and edges on this path/walk. */
    override def size: Int = 2 * length + 1

    def startNode: NodeT
    def endNode: NodeT

    override def iterator: Iterator[InnerElem] = new AbstractIterator[InnerElem] {
      private[this] var start, edgeToFollow = true
      private[this] val nodeIt              = nodes.iterator
      private[this] val firstNode           = nodeIt.next()
      private[this] val edgeIt              = edges.iterator

      def hasNext: Boolean = start || !edgeToFollow || edgeIt.hasNext
      def next(): InnerElem =
        if (start) {
          start = false
          firstNode
        } else if (edgeToFollow) {
          edgeToFollow = false
          edgeIt.next()
        } else {
          edgeToFollow = true
          nodeIt.next()
        }
    }

    /** Returns whether the nodes and edges of this walk are valid with respect
      *  to this graph. $SANECHECK
      */
    def isValid: Boolean = {
      val valid = nodeValidator
      nodes.headOption exists { startNode =>
        val (nodesIt, edgesIt) = (nodes.tail.iterator, edges.iterator)

        @tailrec def ok(prev: NodeT, count: Int): Boolean =
          if (nodesIt.hasNext && edgesIt.hasNext) {
            val node = nodesIt.next()
            if (
              valid(prev) &&
              edgesIt.next().matches((n: NodeT) => n eq prev, (n: NodeT) => n eq node)
            )
              ok(node, count + 1)
            else false
          } else if (nodesIt.isEmpty && edgesIt.isEmpty) {
            count > 0
          } else false

        ok(startNode, 0)
      }
    }

    protected trait NodeValidator extends (NodeT => Boolean)

    protected def nodeValidator: NodeValidator = _ => true

    /** The `Graph` instance that contains `this` walk. */
    final def containingGraph: thisGraph.type = thisGraph

    def toOuterElems: Iterable[scalax.collection.OuterElem[N, E]] = this map {
      case InnerNode(_, n) => scalax.collection.OuterNode[N](n)
      case InnerEdge(_, e) => scalax.collection.OuterEdge[N, E](e)
    }
  }

  object Walk {
    protected[GraphTraversal] trait Zero {
      this: Walk =>
      protected def single: NodeT
      val nodes            = List(single)
      def edges            = Nil
      def startNode        = single
      def endNode          = single
      override def isValid = true
    }

    /** A walk of zero length that is a single node. */
    def zero(node: NodeT) =
      new Walk with Zero {
        final protected def single = node
      }
  }

  /** A `Builder` for valid walks in this graph.
    *
    * $BUILDERADDS walk.
    *
    * A node addition fails if the node to be added is not a direct successor of the
    * previously added node or of the target node of the previously added edge.
    * An edge addition fails if the edge to be added is not an outgoing edge from the
    * previously added node or of the target node of the previously added edge.
    *
    * $BUILDERREC
    *
    * @define ADDELEM Tries to add `elem` to the tail of the path/walk.
    * @define ADDNODE Tries to add `node` to the tail of the path/walk.
    * @define ADDEDGE Tries to add `edge` to the tail of the path/walk.
    * @define ADDSUCCESS Whether the addition was successful.
    */
  trait WalkBuilder extends Builder[InnerElem, Walk] {

    /** The node this walk starts at. */
    def start: NodeT

    /** $ADDELEM
      *  @return $ADDSUCCESS
      */
    final def add(elem: InnerElem): Boolean = elem match {
      case InnerNode(n, _) => this add n
      case InnerEdge(e, _) => this add e
    }

    /** $ADDNODE
      *  @return $ADDSUCCESS
      */
    def add(node: NodeT): Boolean

    /** $ADDEDGE
      *  @return $ADDSUCCESS
      */
    def add(edge: EdgeT): Boolean

    /** $ADDELEM */
    def addOne(elem: InnerElem): this.type = { add(elem); this }

    /** $ADDNODE */
    @inline final def +=(node: NodeT): this.type = { add(node); this }

    /** $ADDEDGE */
    @inline final def +=(edge: EdgeT): this.type = { add(edge); this }
  }

  /** Instantiates a [[WalkBuilder]] for this graph.
    *
    * @param start The node this walk starts at.
    * @param sizeHint Expected maximum number of nodes on this walk.
    * @param edgeSelector $EDGESELECTOR
    */
  def newWalkBuilder(start: NodeT)(implicit
      sizeHint: Int = defaultPathSize,
      edgeSelector: (NodeT, NodeT) => Option[EdgeT] = anyEdgeSelector
  ): WalkBuilder

  /** Represents a path in this graph where
    *
    * `path` $PATHSYNTAX
    *
    * Nodes and edges on the path are distinct. $WALKPATH
    */
  trait Path extends Walk {
    override protected def className = "Path"

    /** Returns whether the nodes and edges on this path are valid with respect
      *  to this graph. $SANECHECK
      */
    override def isValid: Boolean = super.isValid

    override protected def nodeValidator: NodeValidator =
      new NodeValidator {
        private[this] val nodeSet       = new EqHashMap[NodeT, Null](nodes.size)
        def apply(node: NodeT): Boolean = nodeSet.put(node, null).isEmpty
      }
  }
  object Path extends Serializable {

    /** A path of zero length that is a single node. */
    def zero(node: NodeT) =
      new Path with Walk.Zero {
        final protected def single = node
      }
  }

  /** A `Builder` for valid paths in this graph.
    *
    * $BUILDERADDS path.
    *
    * A node addition fails if either the node to be added is already contained or
    * the node is not a direct successor of the previously added node or
    * of the target node of the previously added edge.
    * An edge addition fails if either the edge to be added is is already contained or
    * the edge is not an outgoing edge from the previously added node or
    * of the target node of the previously added edge.
    *
    * $BUILDERREC
    */
  trait PathBuilder extends WalkBuilder with Builder[InnerElem, Path]

  /** Instantiates a [[PathBuilder]] for this graph.
    *
    * @param start The node this path starts at.
    * @param sizeHint Expected maximum number of nodes on this path.
    * @param edgeSelector $EDGESELECTOR
    */
  def newPathBuilder(start: NodeT)(implicit
      sizeHint: Int = defaultPathSize,
      edgeSelector: (NodeT, NodeT) => Option[EdgeT] = anyEdgeSelector
  ): PathBuilder

  /** Represents a cycle in this graph listing the nodes and connecting edges on it
    *  with the following syntax:
    *
    * `cycle ::= ''start-end-node'' { ''edge'' ''node'' } ''edge'' ''start-end-node''`
    *
    * All nodes and edges on the path are distinct except the start and end nodes that
    * are equal. A cycle contains at least a start node followed by any number of
    * consecutive pairs of an edge and a node and the end node equaling to the start node.
    * The first element is the start node, the second is an edge with its tail
    * being the start node and its head being the third element etc.
    */
  trait Cycle extends Path {
    final override protected def className = "Cycle"

    override def endNode: NodeT = startNode

    /** Same as `sameAs` but also comparing this cycle with any `Iterable`.
      */
    final def sameElements(that: Iterable[_]): Boolean =
      this.size == that.size && {
        val thisList = this.toList
        // thisList.indexOf(that.head) may fail due to asymmetric equality
        val idx = thisList.indexWhere(_ == that.head)
        if (idx >= 0) {
          val thisDoubled = thisList ++ thisList.tail
          val thatList    = that.toList
          (thisDoubled startsWith (thatList, idx)) ||
          (thisDoubled startsWith (thatList.reverse, idx))
        } else false
      }

    /** Semantically compares `this` cycle with `that` cycle. While `==` returns `true`
      *  only if the cycles contain the same elements in the same order, this comparison
      *  returns also `true` if the elements of `that` cycle can be shifted and optionally
      *  reversed such that their elements have the same order. For instance, given
      *
      * `c1 = Cycle(1-2-3-1)`, `c2 = Cycle(2-3-1-2)` and `c3 = Cycle(2-1-3-2)`
      *
      * the following expressions hold:
      *
      * `c1 != c2`, `c1 != c3` but `c1 sameAs c2` and `c1 sameAs c3`.
      */
    final def sameAs(that: GraphTraversal[N, E]#Cycle): Boolean =
      this == that || (that match {
        case that: GraphTraversal[N, E]#Cycle => sameElements(that)
        case _                                => false
      })

  }
  object Cycle {
    def findLoop(node: NodeT): Option[Cycle] =
      node.hook map { hook =>
        new Cycle {
          def nodes: Iterable[NodeT] = List(startNode, endNode)
          def edges: Iterable[EdgeT] = List(hook)
          def startNode: NodeT       = node
        }
      }
    protected[collection] def of(start: NodeT, mid: NodeT): Cycle =
      new Cycle {
        def nodes: Iterable[NodeT] = List(startNode, mid, endNode)
        def edges: Iterable[EdgeT] = {
          val out = {
            val outSet = start outgoingTo mid
            outSet find (_.isDirected) getOrElse outSet.head
          }
          val in = (mid outgoingTo start filterNot (_ eq out)).head
          List(out, in)
        }
        def startNode: NodeT = start
      }
  }

  /** Whether all nodes are pairwise adjacent.
    *
    * @return `true` if this graph is complete, `false` if this graph contains any
    * independent nodes.
    */
  def isComplete = {
    val orderLessOne = order - 1
    nodes forall (_.diSuccessors.size == orderLessOne)
  }

  /** An arbitrary edge between `from` and `to` that is available most efficiently.
    */
  @inline final def anyEdgeSelector(from: NodeT, to: NodeT): Option[EdgeT] =
    from findOutgoingTo to

  /** Stores a value and an edge weight function
    *  for use in weight-based traversals that may be defined by `withMaxWeight`.
    */
  class Weight(val value: Double, val edgeWeight: EdgeT => Double) {
    val ordering = EdgeOrdering(edgeWeight)
  }
  object Weight {

    /** Creates a new `Weight` with the given `value` and weight function. */
    def apply[W](value: W, edgeWeight: EdgeT => W)(implicit num: Numeric[W]): Weight = {
      import num._
      new Weight(toDouble(value), edgeWeight andThen toDouble)
    }

    /** Creates a new `Weight` with the given `value` and the default weight function returning `edge.weight`, */
    def apply(value: Long) = new Weight(value.toDouble, BaseInnerEdge.defaultWeight _)
  }

  /** Template for extended node visitors.
    *  While the default node visitor of the type `NodeT => U`
    *  passes solely the inner node being visited, extended node visitors
    *  pass the following traversal state information:
    *  1. the inner node currently visited as with a standard node visitor
    *  1. the number of nodes visited so far and
    *  1. the current depth in terms of the underlying algorithm and
    *  1. a reference to a specific informer that may be pattern matched
    *     to collect even further data specific to the implementation.
    */
  trait ExtendedNodeVisitor[U] extends (NodeT => U) {
    def apply(n: NodeT, cnt: Int, depth: Int, inf: => NodeInformer): U
    def apply(node: NodeT) = apply(node, 0, 0, NodeInformer.empty)
  }
  object ExtendedNodeVisitor {

    /** Instantiates an extended node visitor based on 'visitor'.
      */
    def apply[N, E <: Edge[N], U](visitor: (NodeT, Int, Int, => NodeInformer) => U) =
      new ExtendedNodeVisitor[U] {
        def apply(n: NodeT, cnt: Int, depth: Int, inf: => NodeInformer) =
          visitor(n, cnt, depth, inf)
      }
  }

  type NodeT <: TraverserInnerNode
  trait TraverserInnerNode extends BaseInnerNode { this: NodeT =>

    /** Instantiates an [[InnerNodeTraverser]] $EXTENDSTYPE `NodeT` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def innerNodeTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.innerNodeTraverser(this, parameters)

    /** Instantiates an [[OuterNodeTraverser]] $EXTENDSTYPE `N` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def outerNodeTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.outerNodeTraverser(this, parameters)

    /** Instantiates an [[InnerEdgeTraverser]] $EXTENDSTYPE `EdgeT` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def innerEdgeTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.innerEdgeTraverser(this, parameters)

    /** Instantiates an [[OuterEdgeTraverser]] $EXTENDSTYPE `E[N]` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def outerEdgeTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.outerEdgeTraverser(this, parameters)

    /** Instantiates an [[InnerElemTraverser]] $EXTENDSTYPE `InnerElem` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def innerElemTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.innerElemTraverser(this, parameters)

    /** Instantiates an [[OuterElemTraverser]] $EXTENDSTYPE `OuterElem` $SETROOT. $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def outerElemTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.outerElemTraverser(this, parameters)

    /** Instantiates an [[InnerNodeDownUpTraverser]] $EXTENDSTYPE `(Boolean, NodeT)` $SETROOT.
      *  $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def innerNodeDownUpTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.innerNodeDownUpTraverser(this, parameters)

    /** Instantiates an [[OuterNodeDownUpTraverser]] $EXTENDSTYPE `(Boolean, N)` $SETROOT.
      *  $TOSTART
      *  @param parameters $PARAMETERS
      */
    @inline final def outerNodeDownUpTraverser(implicit parameters: Parameters = Parameters()) =
      thisGraph.outerNodeDownUpTraverser(this, parameters)
  }
  @transient object TraverserInnerNode {
    /* The n parameter should be of type NodeT but then Scaladoc doesn't show implicit members
     * as expected. So TraverserInnerNode is given instead with the drawback of a cast:(.
     */
    implicit def toDefaultTraverser(n: TraverserInnerNode): TraverserMethods[NodeT, InnerNodeTraverser] =
      innerNodeTraverser(n.asInstanceOf[NodeT])
  }

  /** Properties controlling the scope of traversals. */
  protected trait SubgraphProperties {

    /** $SUBGRAPHNODES */
    def subgraphNodes: NodePredicate

    /** $SUBGRAPHEDGES */
    def subgraphEdges: EdgePredicate
  }
  protected object SubgraphProperties {
    def apply[A](t: Iterable[A], nodeFilter: NodePredicate, edgeFilter: EdgePredicate) =
      new ForeachBasedDetachingIterable[A] with SubgraphProperties {
        protected def autarkicForeach[U](f: A => U): Unit = t foreach f

        final protected def sizeHint: Int = 64
        final def subgraphNodes           = nodeFilter
        final def subgraphEdges           = edgeFilter
      }
  }

  /** Properties controlling traversals. */
  protected trait Properties extends SubgraphProperties {

    /** $ROOT */
    def root: NodeT

    /** $PARAMETERS */
    def parameters: Parameters

    /** $ORD */
    def ordering: ElemOrdering

    /** $MAXWEIGHT */
    def maxWeight: Option[Weight] = None
  }

  /** [[Properties]] and methods for creating modified properties in a fluent-interface manner.
    *
    * @define UPDATED Creates a new [[FluentProperties]] based on `this` except for an updated
    */
  abstract protected class FluentProperties[+C <: FluentProperties[C]] {
    this: C with Properties =>

    protected def newTraverser: (NodeT, Parameters, NodePredicate, EdgePredicate, ElemOrdering, Option[Weight]) => C

    /** $UPDATED `parameters`. */
    final def withParameters(parameters: Parameters): C =
      if (this.parameters == parameters) this
      else newTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering, maxWeight)

    /** $UPDATED `subgraphNodes` and/or `subgraphEdges`. */
    final def withSubgraph(nodes: NodePredicate = anyNode, edges: EdgePredicate = anyEdge): C =
      if (
        (this.subgraphNodes eq nodes) &&
        (this.subgraphEdges eq edges)
      ) this
      else newTraverser(root, parameters, nodes, edges, ordering, maxWeight)

    /** $UPDATED `ordering`. */
    final def withOrdering(ordering: ElemOrdering): C =
      if (this.ordering eq ordering) this
      else newTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering, maxWeight)

    /** $UPDATED `kind`. */
    final def withKind(kind: Kind): C =
      withParameters(parameters.withKind(kind))

    /** $UPDATED `direction`. Note that methods returning a Cycle or Path accept only `Successors`. */
    final def withDirection(direction: Direction): C =
      withParameters(parameters.withDirection(direction))

    /** $UPDATED `maxDepth`. */
    final def withMaxDepth(maxDepth: Int): C =
      withParameters(parameters.withMaxDepth(maxDepth))

    /** $UPDATED `maxWeight`. */
    def withMaxWeight(maxWeight: Option[Weight]): C =
      if (this.maxWeight eq maxWeight) this
      else newTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering, maxWeight)

    /** $UPDATED `maxWeight` having the given `max` value and the given weight function. */
    final def withMaxWeight[W: Numeric](max: W, edgeWeight: EdgeT => W): C =
      withMaxWeight(Some(Weight(max, edgeWeight)))

    /** $UPDATED `maxWeight` having the given `max` and the default weight function returning `edge.weight`. */
    final def withMaxWeight(max: Long): C =
      withMaxWeight(Some(Weight(max)))

    final def toInnerElemTraverser(root: NodeT): InnerElemTraverser =
      innerElemTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering, maxWeight)
  }

  /** Represents a component of `this` graph.
    *  Edges and bridges are computed lazily.
    *  Components will be instantiated by [[componentTraverser]] or [[strongComponentTraverser]].
    */
  abstract class Component protected extends Properties {

    def nodes: Set[NodeT]

    final lazy val (edges: Set[EdgeT], frontierEdges: Set[EdgeT]) = {
      val edges   = new ArrayBuffer[EdgeT](thisGraph.size / 2)
      val bridges = new ArrayBuffer[EdgeT](if (mayHaveFrontierEdges) max(thisGraph.size / 100, 16) else 0)
      for (n <- nodes) n.edges.withFilter(subgraphEdges) foreach { e =>
        if (nonBridge(e)) edges += e
        else bridges += e
      }
      (new EqSetFacade(edges), new EqSetFacade(bridges))
    }

    final def frontierEdges(that: Component): Set[EdgeT] =
      if (this.mayHaveFrontierEdges && that.mayHaveFrontierEdges) {
        // optimize calls of 'contains' because EqSetFacade's is O(N)
        def toEqSet(bridges: Set[EdgeT]) =
          bridges.foldLeft(new EqHashSet[EdgeT](bridges.size))((eqSet, e) => eqSet += e)
        val (left, right) =
          if (this.frontierEdges.size > that.frontierEdges.size) (this.frontierEdges, toEqSet(that.frontierEdges))
          else (toEqSet(this.frontierEdges), that.frontierEdges)
        new EqSetFacade(left intersect right)
      } else Set.empty

    final def to[
        G[NN, EE <: Edge[NN]] <: AnyGraph[NN, EE] with GraphLike[NN, EE, G]
    ](factory: GenericGraphCoreFactory[G]): G[N, E] = thisGraph match {
      case g: AnyGraph[N, E] =>
        factory.from(nodes.map(_.outer), edges.map(_.outer))(factory.defaultConfig)
    }

    protected def mayHaveFrontierEdges: Boolean
    final private def nonBridge(e: EdgeT): Boolean = e.ends forall nodes.contains

    protected def className: String
    override def toString = s"$className(${nodes mkString ", "})"
  }

  /** Controls the properties of graph traversals with no specific root and allows
    *  you to produce the (weakly) connected components by a traversal or
    *  call methods like `findCycle` that work component-wise.
    */
  abstract class ComponentTraverser protected
      extends FluentProperties[ComponentTraverser]
      with Properties
      with Iterable[Component] {

    def findCycle[U](implicit visitor: InnerElem => U = Visitor.empty): Option[Cycle]

    /** See [[GraphTraversal#topologicalSort]]. */
    def topologicalSort[U](implicit visitor: InnerElem => U = Visitor.empty): TopologicalSort

    /** See [[GraphTraversal#topologicalSortByComponent]]. */
    def topologicalSortByComponent[U](implicit
        visitor: InnerElem => U = Visitor.empty
    ): Iterable[TopologicalSort]
  }

  /** Creates a [[ComponentTraverser]] responsible for invoking graph traversal methods in all
    *  (weakly) connected components of this possibly disconnected graph.
    *
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def componentTraverser(
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): ComponentTraverser

  /** Controls the properties of graph traversals with no specific root and allows
    *  you to produce the strongly connected components by a traversal.
    */
  abstract class StrongComponentTraverser protected
      extends FluentProperties[StrongComponentTraverser]
      with Properties
      with Iterable[Component]

  /** Creates a [[StrongComponentTraverser]].
    *
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def strongComponentTraverser(
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): StrongComponentTraverser

  /** The `root`-related methods [[Traverser]] will inherit.
    *
    * @define SHORTESTPATH Finds the shortest path from `root` to `potentialSuccessor`
    *         $CONSIDERING The calculation is based on the weight of the edges on the path.
    *         Edges have a default weight of `1L` that can be overridden by custom edges.
    *         A weight function yielding any numeric type may also be passed to `shortestPathTo`.
    * @define POTENTIALSUCC The node the shortest path is to be found to.
    * @define SHORTESTPATHRET The shortest path to `potentialSuccessor` or `None` if either
    *         a. there exists no path to `potentialSuccessor` or
    *         a. there exists a path to `potentialSuccessor` but $DUETOSUBG
    * @define DUETOSUBG due to withSubgraph settings this path was out of scope.
    * @define VISITORDURING Function to be called for each inner node or inner edge visited during the
    */
  abstract protected class TraverserMethods[A, +CC <: TraverserMethods[A, CC]] extends FluentProperties[CC] {
    this: CC with Properties =>

    def root: NodeT

    protected def nodeVisitor[U](f: A => U): (NodeT) => U
    protected def edgeVisitor[U](f: A => U): (EdgeT) => U

    /** $UPDATED `root`. */
    final def withRoot(root: NodeT): CC =
      if (this.root eq root) this
      else newTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering, maxWeight)

    protected def apply[U](pred: NodePredicate = noNode, visitor: A => U = empty): Option[NodeT]

    final protected def requireSuccessors[A](block: => A): A = {
      val direction = parameters.direction
      require(direction eq Successors, s"Found $direction but only $Successors will be accepted.")
      block
    }

    /** Finds a successor of `root` for which the predicate `pred` holds $CONSIDERING
      *  `root` itself does not count as a match. This is also true if it has a hook.
      *  If several successors holding `pred` exist any one of them may be returned.
      *
      * @param pred The predicate which must hold for the resulting node.
      * @param visitor $OPTVISITOR
      * @return A node with the predicate `pred` or `None` if either
      *         a. there is no node with `pred` or
      *         a. there exists no path to such a node or
      *         a. there exists a path to such a node but $DUETOSUBG
      */
    final def findSuccessor[U](pred: NodePredicate)(implicit visitor: A => U = empty): Option[NodeT] =
      apply(pred, visitor)

    /** Checks whether `potentialSuccessor` is a successor of this node $CONSIDERING
      *  Same as `isPredecessorOf`.
      *
      * @param potentialSuccessor The node which is potentially a successor of this node.
      * @param visitor $OPTVISITOR
      * @return `true` if a path exists from this node to `potentialSuccessor` and
      *         it had not to be excluded due to a `subgraph*` restriction.
      */
    @inline final def hasSuccessor[U](potentialSuccessor: NodeT)(implicit visitor: A => U = empty): Boolean =
      findSuccessor(_ eq potentialSuccessor)(visitor).isDefined

    /** Same as `hasSuccessor`. */
    @inline final def isPredecessorOf[U](potentialSuccessor: NodeT)(implicit visitor: A => U = empty): Boolean =
      hasSuccessor(potentialSuccessor)(visitor)

    /** Finds a predecessor of `root` for which the predicate `pred` holds $CONSIDERING
      * `root` itself does not count as a match. This is also true if it has a hook.
      * If several predecessors exist the algorithm selects the first of them found.
      *
      * @param pred The predicate which must hold true for the resulting node.
      * @param visitor $OPTVISITOR
      * @return A node with the predicate `pred` or `None` if either
      *         a. there is no node with `pred` or
      *         a. there exists no path from such a node to this node or
      *         a. there exists a path from such a node to `root` but $DUETOSUBG
      */
    final def findPredecessor[U](pred: NodePredicate)(implicit visitor: A => U = empty): Option[NodeT] =
      withParameters(parameters.withDirection(Predecessors))(pred, visitor)

    /** Checks whether `potentialPredecessor` is a predecessor of `root` $CONSIDERING
      *  Same as `isSuccessorOf`.
      *
      * @param potentialPredecessor The node which is potentially a predecessor of `root`.
      * @param visitor $OPTVISITOR
      * @return `true` if a path exists from `potentialPredecessor` to `root` and
      *         it had not to be excluded due to `subgraph` properties.
      */
    @inline final def hasPredecessor[U](potentialPredecessor: NodeT)(implicit visitor: A => U = empty): Boolean =
      findPredecessor(_ eq potentialPredecessor)(visitor).isDefined

    /** Same as `hasPredecessor`. */
    @inline final def isSuccessorOf[U](potentialPredecessor: NodeT)(implicit visitor: A => U = empty): Boolean =
      hasPredecessor(potentialPredecessor)(visitor)

    /** Finds a node connected with `root` by any number of edges with any direction
      *  for which the predicate `pred` holds $CONSIDERING
      *  For directed or mixed graphs the node to be found is weakly connected with this node.
      * `root` itself does not count as a match. This is also true if it has a hook.
      * If several connected nodes exist with `pred` the algorithm selects any one of these.
      *
      * @param pred The predicate which must hold true for the resulting node.
      * @param visitor $OPTVISITOR
      * @return A node with the predicate `pred` or `None` if either
      *         a. there is no node with `pred` or
      *         a. there exists no connection to such a node or
      *         a. there exists a connection to such a node but $DUETOSUBG
      */
    final def findConnected[U](pred: NodePredicate)(implicit visitor: A => U = empty): Option[NodeT] =
      withParameters(parameters.withDirection(AnyConnected))(pred, visitor)

    /** Checks whether `potentialConnected` is a node (not necessarily directly)
      *  connected with `root` by any number of edges with any direction $CONSIDERING
      *  For directed or mixed graphs it is satisfactory that `potentialConnected` is
      *  weakly connected with `root`.
      *
      * @param potentialConnected The node which is potentially connected with `root`.
      * @param visitor $OPTVISITOR
      * @return `true` if a path exists from this node to `potentialConnected` and
      *         it had not to be excluded due to `subgraph` properties.
      */
    @inline final def isConnectedWith[U](potentialConnected: NodeT)(implicit visitor: A => U = empty): Boolean =
      findConnected(_ eq potentialConnected)(visitor).isDefined

    /** Finds a path from `root` to a successor of `root` for which `pred` holds $CONSIDERING
      * `root` itself does not count as a match. This is also true if it has a hook.
      * If several successors exist the algorithm selects any one of these.
      *
      * @param pred The predicate which must hold true for the successor.
      * @param visitor $OPTVISITOR
      * @return A path to a node with the predicate `pred` or `None` if either
      *         a. there is no node with `pred` or
      *         a. there exists no path to such a node or
      *         a. there exists a path to such a node but $DUETOSUBG
      */
    def pathUntil[U](pred: NodePredicate)(implicit visitor: A => U = empty): Option[Path]

    /** Finds a path from `root` to `potentialSuccessor` $CONSIDERING
      *
      * @param potentialSuccessor The node a path is to be found to.
      * @param visitor $OPTVISITOR
      * @return A path to `potentialSuccessor` or `None` if either
      *         a. there is no node with `pred` or
      *         a. there exists no path to such a node
      */
    final def pathTo[U](potentialSuccessor: NodeT)(implicit visitor: A => U = empty): Option[Path] = requireSuccessors {
      if (potentialSuccessor eq root) Some(Path.zero(root))
      else pathUntil(_ eq potentialSuccessor)(visitor)
    }

    /** $SHORTESTPATH
      *
      * @param potentialSuccessor $POTENTIALSUCC
      * @param visitor $OPTVISITOR
      * @return $SHORTESTPATHRET
      */
    @inline final def shortestPathTo[U](potentialSuccessor: NodeT)(implicit visitor: A => U = empty): Option[Path] =
      shortestPathTo(potentialSuccessor, BaseInnerEdge.defaultWeight, visitor)

    /** $SHORTESTPATH
      *
      * @param potentialSuccessor $POTENTIALSUCC
      * @param weight Function to determine the weight of edges. If supplied, this function
      *        takes precedence over edge weights.
      * @return $SHORTESTPATHRET
      */
    @inline final def shortestPathTo[T: Numeric](potentialSuccessor: NodeT, weight: EdgeT => T): Option[Path] =
      shortestPathTo(potentialSuccessor, weight, empty)

    /** $SHORTESTPATH
      *
      * @param potentialSuccessor $POTENTIALSUCC
      * @param weight Function to determine the weight of edges. If supplied, this function
      *        takes precedence over edge weights.
      * @param visitor $OPTVISITOR
      * @return $SHORTESTPATHRET
      */
    def shortestPathTo[T: Numeric, U](potentialSuccessor: NodeT, weight: EdgeT => T, visitor: A => U): Option[Path]

    /** Finds a cycle starting the search at `root` $INTOACC, if any.
      *  The resulting cycle may start at any node connected with `this` node.
      *
      * @param visitor $OPTVISITOR
      * @return A cycle or `None` if either
      *  a. there exists no cycle in the component depicting by `root` or
      *  a. there exists a cycle in the component but $DUETOSUBG
      */
    def findCycle[U](implicit visitor: A => U = empty): Option[Cycle]

    /** Finds a cycle that contains `root` $INTOACC.
      * Irrespective of the current setting for `kind`, `DepthFirst` is used internally.
      *
      * @param visitor $OPTVISITOR
      * @return A cycle containing `root` or `None` if either
      *  a. there exists no cycle containing `root` or
      *  a. there exists such a cycle but $DUETOSUBG
      */
    def partOfCycle[U](implicit visitor: A => U = empty): Option[Cycle]

    /** Sorts the component designated by this node topologically.
      *  Only nodes connected with this node will be included in the resulting topological order.
      *  If the graph is known to be connected choose [[GraphTraversal#topologicalSort]] instead.
      *  $SEEFLUENT
      *
      *  @param ignorePredecessors If `true`, the topological sort will be partial in that it will only
      *                            include successors of `root`. `withSubgraph` restricts the successor nodes to
      *                            be included but not predecessors that will be excluded in total.
      *  @param visitor            $VISITORDURING sort.
      */
    def topologicalSort[U](ignorePredecessors: Boolean = false)(implicit
        visitor: InnerElem => U = empty
    ): TopologicalSort

    /** Determines the weak component that contains this node.
      *  $SEEFLUENT
      *
      *  @param visitor $VISITORDURING search.
      */
    def weakComponent[U](implicit visitor: A => U = empty): Component

    /** Finds all strongly connected components reachable from this node.
      *  $SEEFLUENT
      *
      *  @param visitor $VISITORDURING search.
      */
    def strongComponents[U](implicit visitor: A => U = empty): Iterable[Component]
  }

  /** Controls the properties of consecutive graph traversals starting at a root node.
    *  Provides methods to refine the properties and to invoke traversals.
    *  Instances will be created by [[innerNodeTraverser]] etc.
    */
  trait Traverser[A, +CC <: Traverser[A, CC]]
      extends TraverserMethods[A, CC]
      with Properties
      with ForeachBasedDetachingIterable[A] {
    this: CC =>

    protected def autarkicForeach[U](f: A => U): Unit =
      if (subgraphNodes(root))
        apply(noNode, f)

    /** Completes a traversal and creates a new connected graph populated with the
      *  elements visited.
      */
    final def to[CC[N, E <: Edge[N]] <: AnyGraph[N, E] with GraphLike[N, E, CC]](
        factory: generic.GenericGraphCoreFactory[CC]
    ): CC[N, E] = thisGraph match {
      case _: AnyGraph[N, E] =>
        val b = factory.newBuilder[N, E](factory.defaultConfig)
        b += root
        val edgeTraverser = this match {
          case e: InnerEdgeTraverser => e
          case _                     => innerEdgeTraverser(root, parameters, subgraphNodes, subgraphEdges, ordering)
        }
        edgeTraverser foreach { e: EdgeT =>
          b += e
        }
        b.result
    }

    final protected def nonOverflowingOrderMultipliedBy_2: Int =
      if (order > (Int.MaxValue >> 1)) order else order << 1
  }

  /** Controls the properties of inner-node graph traversals. $TOSTART
    */
  abstract class InnerNodeTraverser extends Traverser[NodeT, InnerNodeTraverser] {
    final protected def sizeHint: Int = order
  }

  /** Creates a [[InnerNodeTraverser]] based on `scala.collection.Iterable[NodeT]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def innerNodeTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): InnerNodeTraverser

  /** Controls the properties of outer-node graph traversals. $TOSTART
    */
  abstract class OuterNodeTraverser extends Traverser[N, OuterNodeTraverser] {
    final protected def sizeHint: Int = order
  }

  /** Creates a [[OuterNodeTraverser]] based on `scala.collection.Iterable[N]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def outerNodeTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): OuterNodeTraverser

  /** Controls the properties of inner-edge graph traversals. $TOSTART
    */
  abstract class InnerEdgeTraverser extends Traverser[EdgeT, InnerEdgeTraverser] {
    final protected def sizeHint: Int = thisGraph.size
  }

  /** Creates a [[InnerEdgeTraverser]] based on `scala.collection.Iterable[EdgeT]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    */
  def innerEdgeTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): InnerEdgeTraverser

  /** Controls the properties of outer-edge graph traversals. $TOSTART
    */
  abstract class OuterEdgeTraverser extends Traverser[E, OuterEdgeTraverser] {
    final protected def sizeHint: Int = thisGraph.size
  }

  /** Creates a [[OuterEdgeTraverser]] based on `scala.collection.Iterable[E[N]]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def outerEdgeTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): OuterEdgeTraverser

  /** Controls the properties of inner-element graph traversals. $TOSTART
    */
  abstract protected class InnerElemTraverser extends Traverser[InnerElem, InnerElemTraverser] {
    final protected def sizeHint: Int = order + thisGraph.size
  }

  /** Creates a [[InnerElemTraverser]] based on `scala.collection.Iterable[InnerElem]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def innerElemTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): InnerElemTraverser

  /** Controls the properties of outer-element graph traversals. $TOSTART
    */
  trait OuterElemTraverser extends Traverser[OuterElem, OuterElemTraverser] {
    final protected def sizeHint: Int = order + thisGraph.size
  }

  /** Creates a [[OuterElemTraverser]] based on `scala.collection.Iterable[OuterElem]`.
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def outerElemTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): OuterElemTraverser

  /** Controls the properties of inner-node down-up graph traversals. $TOSTART
    */
  abstract class InnerNodeDownUpTraverser extends Traverser[(Boolean, NodeT), InnerNodeDownUpTraverser] {
    final protected def sizeHint: Int = nonOverflowingOrderMultipliedBy_2
  }

  /** Creates a [[InnerNodeDownUpTraverser]] based on `scala.collection.Iterable[(Boolean, NodeT)]`
    *  $DOWNUPBOOLEAN
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS A `kind` different from `DepthFirst` will be ignored.
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    * @param maxWeight $MAXWEIGHT
    */
  def innerNodeDownUpTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): InnerNodeDownUpTraverser

  /** Controls the properties of outer-node down-up graph traversals. $TOSTART
    */
  abstract class OuterNodeDownUpTraverser extends Traverser[(Boolean, N), OuterNodeDownUpTraverser] {
    final protected def sizeHint: Int = nonOverflowingOrderMultipliedBy_2
  }

  /** Creates a [[OuterNodeDownUpTraverser]] based on `scala.collection.Iterable[(Boolean, N)]`
    *  $DOWNUPBOOLEAN
    *
    * @param root $ROOT
    * @param parameters $PARAMETERS A `kind` different from `DepthFirst` will be ignored.
    * @param subgraphNodes $SUBGRAPHNODES
    * @param subgraphEdges $SUBGRAPHEDGES
    * @param ordering $ORD
    */
  def outerNodeDownUpTraverser(
      root: NodeT,
      parameters: Parameters = Parameters(),
      subgraphNodes: NodePredicate = anyNode,
      subgraphEdges: EdgePredicate = anyEdge,
      ordering: ElemOrdering = NoOrdering,
      maxWeight: Option[Weight] = None
  ): OuterNodeDownUpTraverser

  object Informer {
    type Depth = Int

    trait NodeElement {
      def node: NodeT
    }

    /** Extended node visitor informer for depth first searches.
      */
    trait DfsInformer extends NodeInformer {
      import DfsInformer._
      def stackIterator: DfsStack
      def pathIterator: DfsPath
    }
    object DfsInformer {
      case class Element protected[collection] (node: NodeT, depth: Depth, cumWeight: Double = 0) extends NodeElement
      object Element {
        def apply(node: NodeT) = new Element(node, 0)
      }
      type DfsStack = Iterator[Element]
      type DfsPath  = Iterator[Element]
      def unapply(inf: DfsInformer): Option[(DfsStack, DfsPath)] = Some(inf.stackIterator, inf.pathIterator)
    }

    case class CycleStackElem(node: NodeT, edges: Iterable[EdgeT]) extends NodeElement
    object CycleStackElem {
      def apply(node: NodeT) = new CycleStackElem(node, Nil)
    }

    /** Extended node visitor informer for cycle detecting.
      *  This informer always returns `0` for `depth`.
      */
    trait WgbInformer extends NodeInformer {
      import WgbInformer._
      def stackIterator: WgbStack
      def pathIterator: WgbPath
    }
    object WgbInformer {
      // exclude and multiEdges only needed for undirected edges
      case class Element protected[collection] (
          node: NodeT,
          predecessor: NodeT,
          exclude: Boolean,
          multiEdges: Iterable[EdgeT],
          cumWeight: Double = 0
      ) extends NodeElement
      type WgbStack = Iterator[Element]
      type WgbPath  = Iterator[CycleStackElem]
      def unapply(inf: WgbInformer): Option[(WgbStack, WgbPath)] = Some(inf.stackIterator, inf.pathIterator)
    }

    /** Extended node visitor informer for breath first searches.
      */
    trait BfsInformer extends NodeInformer {
      import BfsInformer._
      def queueIterator: BfsQueue
    }
    object BfsInformer {
      type Element = DfsInformer.Element
      val Element = DfsInformer.Element
      type BfsQueue = Iterator[Element]
      def unapply(inf: BfsInformer): Option[BfsQueue] = Some(inf.queueIterator)
    }

    /** Extended node visitor informer for calculating shortest paths.
      *  This informer always returns `0` for `depth`.
      */
    abstract class DijkstraInformer[T: Numeric] extends NodeInformer {
      import DijkstraInformer._
      def queueIterator: DijkstraQueue[T]
      def costsIterator: DijkstraCosts[T]
    }
    object DijkstraInformer {
      case class Element[T: Numeric] protected[collection] (node: NodeT, cumWeight: T, depth: Depth) extends NodeElement
      type DijkstraQueue[T] = Iterator[Element[T]]
      type DijkstraCosts[T] = Iterator[(NodeT, T)]
      def unapply[T: Numeric](inf: DijkstraInformer[T]): Option[(DijkstraQueue[T], DijkstraCosts[T])] =
        Some(inf.queueIterator, inf.costsIterator)
    }

    /** Extended node visitor informer for Tarjan's algorithm.
      */
    abstract class TarjanInformer(val index: Int, val lowLink: Int) extends NodeInformer {
      import TarjanInformer._
      def stackIterator: TarjanStack
    }
    object TarjanInformer {
      type TarjanStack = Iterator[Element]
      case class Element protected[collection] (
          node: NodeT,
          depth: Depth,
          cumWeight: Double = 0,
          index: Int = 0,
          var lowLink: Int = 0
      ) extends NodeElement
      def unapply[N](inf: TarjanInformer): Option[(Int, Int, TarjanStack)] =
        Some((inf.index, inf.lowLink, inf.stackIterator))
    }
  }
}

/** Contains traversal parameter definitions such as direction constants.
  *
  * @define KIND The kind of traversal including breadth-first and depth-fist search.
  * @define DIRECTION Determines which connected nodes the traversal has to follow.
  *         The default value is `Successors`.
  * @define MAXDEPTH A positive value to limit the number of layers for BFS respectively
  *         the number of consecutive child visits before siblings are visited for DFS.
  *         `0` - the default - indicates that the traversal should have
  *         an unlimited depth.
  * @author Peter Empen
  */
object GraphTraversal {

  /** Algebraic type to determine which connected nodes the traversal has to follow.
    *  The default value is `Successors`.
    *
    *  Note that methods returning a Cycle or Path accept only `Successors`.
    */
  sealed trait Direction

  /** Defines the traversal to follow successor nodes. */
  case object Successors extends Direction

  /** Defines the traversal to follow predecessor nodes. */
  case object Predecessors extends Direction

  /** Defines the traversal to follow successor and predecessor nodes alike. */
  case object AnyConnected extends Direction

  /** Marker trait for informers aimed at passing algorithmic-specific state
    *  to [[scalax.collection.GraphTraversal.ExtendedNodeVisitor]].
    *  Following informers are available:
    *  1. [[scalax.collection.GraphTraversal#Informer.BfsInformer]]
    *  1. [[scalax.collection.GraphTraversal#Informer.DfsInformer]]
    *  1. [[scalax.collection.GraphTraversal#Informer.WgbInformer]]
    *  1. [[scalax.collection.GraphTraversal#Informer.DijkstraInformer]]
    *  1. [[scalax.collection.GraphTraversal#Informer.TarjanInformer]].
    */
  trait NodeInformer
  object NodeInformer extends Serializable {
    def empty = new NodeInformer {}
  }

  /** Algebraic type for the kind of traversal. */
  trait Kind {

    /** Whether 'this' kind equals to BreadthFirst. */
    def isBsf = this eq BreadthFirst
  }

  /** Instructs the traverser to use a breadth first search
    *  (BSF, search layer-for-layer).
    */
  case object BreadthFirst extends Kind

  /** Instructs the traverser to use a depth first search (DFS). */
  case object DepthFirst extends Kind

  /** Parameters to control traversals.
    *
    * @param kind       $KIND
    * @param direction  $DIRECTION
    * @param maxDepth   $MAXDEPTH
    * @define UPDATED Creates a new `Parameters` based on `this` except for an updated
    */
  case class Parameters(kind: Kind = BreadthFirst, direction: Direction = Successors, maxDepth: Int = 0) {

    /** $UPDATED `kind`. */
    def withKind(kind: Kind): Parameters =
      if (this.kind eq kind) this else copy(kind = kind)

    /** $UPDATED `direction`. */
    def withDirection(direction: Direction): Parameters =
      if (this.direction == direction) this else copy(direction = direction)

    /** $UPDATED `maxDepth`. */
    def withMaxDepth(maxDepth: Int): Parameters =
      if (this.maxDepth == maxDepth) this else copy(maxDepth = maxDepth)
  }
  object Parameters {

    /** Default `Parameters`. */
    def apply = new Parameters()

    /** Creates `Parameters` of kind `BreadthFirst` with specific `direction` and `maxDepth`. */
    def Bfs(direction: Direction = Successors, maxDepth: Int = 0) =
      Parameters(direction = direction, maxDepth = maxDepth)

    /** Creates `Parameters` of kind `DepthFirst` with specific `direction` and `maxDepth`. */
    def Dfs(direction: Direction = Successors, maxDepth: Int = 0) =
      Parameters(kind = DepthFirst, direction = direction, maxDepth = maxDepth)
  }

  /** Implements an empty visitor based on a value.
    */
  object Visitor {
    final private val _empty: Any => Unit              = null
    @inline final def empty[A, U]: A => U              = _empty.asInstanceOf[A => U]
    @inline final def isEmpty[A, U](visitor: A => U)   = visitor eq _empty
    @inline final def isDefined[A, U](visitor: A => U) = visitor ne _empty
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy