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

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

The newest version!
package scalax.collection

import scala.annotation.{switch, tailrec}
import scala.collection.{FilterableSet, FilteredSet}
import scala.collection.mutable.{ArrayBuffer, PriorityQueue, Queue, ArrayStack => Stack, Map => MMap}

import scalax.collection.Compat._
import scalax.collection.GraphPredef.EdgeLikeIn
import scalax.collection.immutable.SortedArraySet
import scalax.collection.mutable.{ArraySet, EqHashMap, EqHashSet}

/** Default implementation of the graph algorithms to maintain the functionality
  *  defined by [[GraphTraversal]].
  *
  *  @author Peter Empen
  */
trait TraverserImpl[N, E[+X] <: EdgeLikeIn[X]] {
  thisGraph: GraphTraversalImpl[N, E] =>

  import GraphTraversal._
  import Informer._
  import Visitor._
  import State._

  protected[collection] trait Impl[A, +This <: Traverser[A, This] with Impl[A, This]] extends Traverser[A, This] {
    thisImpl: This =>

    final protected def apply[U](pred: NodeFilter = noNode, visitor: A => U = Visitor.empty): Option[NodeT] =
      Runner[U](pred, visitor)()

    final def findCycle[U](implicit visitor: A => U = Visitor.empty): Option[Cycle] = requireSuccessors {
      cycle(Runner(StopCondition.None, visitor).dfsWGB(), subgraphEdges)
    }

    final def partOfCycle[U](implicit visitor: A => U = Visitor.empty): Option[Cycle] = requireSuccessors {
      cycle(Runner(StopCondition.None, visitor).dfsWGB(mustContain = Some(root)), subgraphEdges)
    }

    final def pathUntil[U](pred: NodeFilter)(implicit visitor: A => U = Visitor.empty): Option[Path] =
      pathUntil_[U](pred, visitor)

    final protected[TraverserImpl] def pathUntil_[U](pred: NodeFilter,
                                                     visitor: A => U = Visitor.empty,
                                                     maybeHandle: Option[Handle] = None): Option[Path] =
      requireSuccessors {
        Runner[U](pred, visitor).dfsStack() match {
          case (target, path) =>
            target map { _ =>
              new AnyEdgeLazyPath(new ReverseStackTraversable[DfsInformer.Element](path), subgraphEdges)
            }
        }
      }

    final def topologicalSort[U](ignorePredecessors: Boolean = false)(
        implicit visitor: InnerElem => U = Visitor.empty): CycleNodeOrTopologicalOrder = {
      val predecessors: MSet[NodeT] =
        if (ignorePredecessors)
          innerNodeTraverser(root, Parameters.Dfs(Predecessors)).toMSet -= root
        else MSet.empty
      def ignore(n: NodeT): Boolean = if (ignorePredecessors) predecessors contains n else false
      val inDegrees =
        forInDegrees(
          innerNodeTraverser(root, Parameters.Dfs(AnyConnected), n => subgraphNodes(n), subgraphEdges),
          includeInDegree = if (ignorePredecessors) !ignore(_) else anyNode,
          includeAnyway = if (ignorePredecessors) Some(root) else None
        )
      val withoutPreds = inDegrees._1.iterator.filterNot(predecessors.contains).toBuffer
      Runner(StopCondition.None, Visitor.empty).topologicalSort(inDegrees.copy(_1 = withoutPreds))
    }

    final def shortestPathTo[T: Numeric, U](potentialSuccessor: NodeT,
                                            weight: EdgeT => T,
                                            visitor: A => U): Option[Path] = requireSuccessors {
      Runner(StopCondition.None, visitor).shortestPathTo(potentialSuccessor, weight)
    }

    final def weakComponent[U](implicit visitor: A => U = Visitor.empty): Component =
      new WeakComponentImpl(
        root,
        parameters,
        subgraphNodes,
        subgraphEdges,
        ordering,
        root.innerNodeTraverser.withDirection(AnyConnected).toSet)

    final def strongComponents[U](implicit visitor: A => U = Visitor.empty): Iterable[Component] = requireSuccessors {
      Runner(StopCondition.None, visitor).dfsTarjan()
    }

    /** Contains algorithms and local values to be used by the algorithms.
      *  Last target reusability and best possible run-time performance.
      *
      *  @param stopAt node predicate marking an end condition for the search
      */
    final protected class Runner[U] private (stopAt: StopCondition, visitor: A => U) {

      private[this] val addMethod = parameters.direction match {
        case Successors   => Node.addDiSuccessors _
        case Predecessors => Node.addDiPredecessors _
        case AnyConnected => Node.addNeighbors _
      }

      private[this] val doNodeFilter = isCustomNodeFilter(subgraphNodes)
      private[this] val doEdgeFilter = isCustomEdgeFilter(subgraphEdges) || maxWeight.isDefined
      private[this] val (doNodeSort, nodeOrdering, reverseNodeOrdering, doEdgeSort, edgeOrdering, reverseEdgeOrdering) =
        ordering match {
          case nO: NodeOrdering   => (true, nO, nO.reverse, false, null, null)
          case eO: EdgeOrdering   => (false, null, null, true, eO, eO.reverse)
          case _: NoOrdering.type => (false, null, null, false, null, null)
        }

      /* doNodeVisitor:  whether any node visitor is to be called
       * nodeVisitor:    the simple node visitor or empty
       * extNodeVisitor: the extended node visitor or null
       * edgeVisitor:    the edge visitor or empty
       */
      private[this] val (doNodeVisitor, nodeVisitor, extNodeVisitor, edgeVisitor): (Boolean, NodeT => U, ExtendedNodeVisitor[U], EdgeT => U) = {
        val nodeVisitor = thisImpl.nodeVisitor(visitor)
        val extNodeVisitor = visitor match {
          case ext: ExtendedNodeVisitor[U] => ext
          case _                           => null
        }
        (
          isDefined(nodeVisitor),
          if (extNodeVisitor eq null) nodeVisitor else Visitor.empty[NodeT, U],
          extNodeVisitor,
          thisImpl.edgeVisitor(visitor))
      }

      private[this] val filteredNodes = parameters.direction match {
        case Successors   => filteredSuccessors _
        case Predecessors => filteredPredecessors _
        case AnyConnected => filteredNeighbors _
      }

      private[this] def directionEdges(n: NodeT): NodeT => Set[EdgeT] with FilterableSet[EdgeT] =
        parameters.direction match {
          case Successors   => n.outgoingTo
          case Predecessors => n.incomingFrom
          case AnyConnected => n.connectionsWith
        }

      @inline protected[Impl] def apply(): Option[NodeT] =
        if (parameters.kind.isBsf) bfs() else dfs()

      @inline private[this] def estimatedNrOfNodes(node: NodeT) = {
        val max = node.edges.size
        if (thisGraph.isHyper) max * 4 else max
      }

      private def maxDepth =
        if (parameters.maxDepth > 0) parameters.maxDepth
        else java.lang.Integer.MAX_VALUE

      private[this] def sorted[A <: InnerElem with B, B <: InnerElem: reflect.ClassTag](
          set: AnySet[A],
          maxOrEst: Int, // maximum size of set or negative for an estimate
          ordering: Ordering[A]): AnySet[A] =
        set match {
          case a: ArraySet[A] => a.sorted(ordering)
          case t =>
            @inline def newArray(len: Int): Array[A] = new Array[B](len).asInstanceOf[Array[A]]
            var cnt                                  = 0
            val arr =
              if (maxOrEst >= 0) {
                val arr = newArray(maxOrEst)
                t foreach { a =>
                  arr(cnt) = a; cnt += 1
                }
                if (maxOrEst > cnt) {
                  val shrinked = newArray(cnt)
                  Array.copy(arr, 0, shrinked, 0, cnt)
                  shrinked
                } else arr
              } else {
                val buf = new ArrayBuffer[A](maxOrEst)
                t foreach { a =>
                  buf += a; cnt += 1
                }
                val arr = newArray(cnt)
                buf copyToArray arr
                arr
              }
            new SortedArraySet[A](arr)(ordering)
        }

      @inline private[this] def sortedNodes(nodes: AnySet[NodeT], maxOrEst: Int, reverse: Boolean): AnySet[NodeT] =
        sorted[NodeT, InnerElem](nodes, maxOrEst, if (reverse) reverseNodeOrdering else nodeOrdering)

      private[this] def filtered(node: NodeT,
                                 nodeFilter: NodeFilter,
                                 _edges: AnySet[EdgeT], // already filtered if adequate
                                 reverse: Boolean): Iterable[NodeT] = {

        val edges = {
          if (doEdgeSort) {
            val maxEdges = node.edges.size
            if (reverse) sorted[EdgeT, InnerElem](_edges, maxEdges, reverseEdgeOrdering)
            else sorted[EdgeT, InnerElem](_edges, maxEdges, edgeOrdering)
          } else _edges
        }

        val filter         = chooseFilter(nodeFilter)
        val doEdgeVisitor  = isDefined(edgeVisitor)
        val estimatedNodes = estimatedNrOfNodes(node)

        def withEdges(withNode: NodeT => Unit): Unit =
          edges foreach { e =>
            addMethod(node, e, withNode)
            if (doEdgeVisitor) edgeVisitor(e)
          }

        if (doEdgeSort) {
          /* The node set to be returned must reflect edge ordering.
           * doEdgeSort and doNodeSort are mutually exclusive.
           */
          val set  = new EqHashSet[NodeT](estimatedNodes)
          val succ = new ArrayBuffer[NodeT](estimatedNodes)
          withEdges(n => if (filter(n) && set.add(n)) succ += n)
          succ
        } else {
          val succ = new EqHashSet[NodeT](estimatedNodes)
          withEdges(n => if (filter(n)) succ += n)
          if (doNodeSort) sortedNodes(succ, succ.size, reverse)
          else succ
        }
      }

      private[this] val withEdgeFiltering: Boolean =
        doEdgeFilter || doEdgeSort || isDefined(edgeVisitor) || maxWeight.isDefined

      @inline private[this] def chooseFilter(nodeFilter: NodeFilter): NodeFilter =
        if (doNodeFilter) (n: NodeT) => nodeFilter(n) && subgraphNodes(n)
        else (n: NodeT) => nodeFilter(n)

      private[this] def filtered(nodes: AnySet[NodeT],
                                 maxNodes: Int,
                                 nodeFilter: NodeFilter,
                                 reverse: Boolean): AnySet[NodeT] = {
        val filtered = new FilteredSet(nodes, chooseFilter(nodeFilter))
        if (doNodeSort) sortedNodes(filtered, maxNodes, reverse)
        else filtered
      }

      private[this] def edgeFilter(cumWeight: Double): EdgeFilter = maxWeight.fold(ifEmpty = subgraphEdges)(w => {
        def weightFilter: EdgeFilter = e => cumWeight + w.edgeWeight(e) <= w.value
        if (isCustomEdgeFilter(subgraphEdges)) e => subgraphEdges(e) && weightFilter(e)
        else weightFilter
      })

      private[this] def minWeight(n: NodeT, neighbor: NodeT, cumWeight: Double): Double =
        maxWeight.fold[Double](ifEmpty = throw new MatchError("maxWeight is expected to be defined."))(
          w => w.edgeWeight(directionEdges(n)(neighbor) withSetFilter edgeFilter(cumWeight) min w.ordering)
        )

      private[this] def filteredEdges(edges: AnySet[EdgeT] with FilterableSet[EdgeT],
                                      cumWeight: Double): AnySet[EdgeT] =
        if (doEdgeFilter) edges withSetFilter edgeFilter(cumWeight)
        else edges

      private[this] def filteredSuccessors(node: NodeT,
                                           nodeFilter: NodeFilter,
                                           cumWeight: Double,
                                           reverse: Boolean): Iterable[NodeT] =
        if (withEdgeFiltering)
          filtered(node, nodeFilter, filteredEdges(node.outgoing, cumWeight), reverse)
        else {
          val succ = node.diSuccessors
          filtered(succ, succ.size, nodeFilter, reverse)
        }

      private[this] def filteredPredecessors(node: NodeT,
                                             nodeFilter: NodeFilter,
                                             cumWeight: Double,
                                             reverse: Boolean): Iterable[NodeT] =
        if (withEdgeFiltering)
          filtered(node, nodeFilter, filteredEdges(node.incoming, cumWeight), reverse)
        else
          filtered(node.diPredecessors, -estimatedNrOfNodes(node), nodeFilter, reverse)

      private[this] def filteredNeighbors(node: NodeT,
                                          nodeFilter: NodeFilter,
                                          cumWeight: Double,
                                          reverse: Boolean): Iterable[NodeT] =
        if (withEdgeFiltering)
          filtered(node, nodeFilter, filteredEdges(node.edges, cumWeight), reverse)
        else
          filtered(node.neighbors, -estimatedNrOfNodes(node), nodeFilter, reverse)

      protected[collection] def shortestPathTo[T: Numeric](potentialSuccessor: NodeT,
                                                           weight: EdgeT => T): Option[Path] =
        withHandle() { implicit visitedHandle =>
          val num: Numeric[T] = implicitly[Numeric[T]]
          import num._
          type PrioQueueElem = DijkstraInformer.Element[T]
          val PrioQueueElem = DijkstraInformer.Element

          implicit def edgeOrdering: Ordering[EdgeT] = Edge.weightOrdering(weight)
          implicit object nodeOrdering extends Ordering[PrioQueueElem] {
            def compare(x: PrioQueueElem, y: PrioQueueElem) = num.compare(y.cumWeight, x.cumWeight)
          }
          @inline def nonVisited(n: NodeT) = !n.visited

          val untilDepth: Int = maxDepth
          val withMaxWeight   = maxWeight.isDefined
          val dest            = MMap[NodeT, T](root -> zero)
          val mapToPred       = MMap[NodeT, NodeT]()
          val qNodes          = PriorityQueue(PrioQueueElem(root, zero, 0))

          def sortedAdjacentNodes(node: NodeT, cumWeight: T, depth: Depth): PriorityQueue[PrioQueueElem] = {
            val predecessorWeight = dest(node)
            filteredSuccessors(node, nonVisited, if (withMaxWeight) cumWeight.toDouble else Double.NaN, false)
              .foldLeft(PriorityQueue.empty[PrioQueueElem])(
                (q, n) =>
                  q += PrioQueueElem(
                    n,
                    predecessorWeight + weight(node.outgoingTo(n).view.filter(subgraphEdges(_)).min),
                    depth)
              )
          }

          def relax(pred: NodeT, succ: NodeT) {
            val cost = dest(pred) +
              weight(pred.outgoingTo(succ).view.filter(subgraphEdges(_)).min)
            if (!dest.isDefinedAt(succ) || cost < dest(succ)) {
              dest += succ      -> cost
              mapToPred += succ -> pred
            }
          }

          var nodeCnt = 0
          @tailrec def rec(pq: PriorityQueue[PrioQueueElem]) {
            if (pq.nonEmpty && (pq.head.node ne potentialSuccessor)) {
              val PrioQueueElem(node, cumWeight, depth) = pq.dequeue
              if (!node.visited) {
                val ordNodes =
                  if (depth == untilDepth) PriorityQueue.empty[PrioQueueElem]
                  else sortedAdjacentNodes(node, cumWeight, depth + 1)
                pq ++= ordNodes

                @tailrec def loop(pq2: PriorityQueue[PrioQueueElem]) {
                  if (pq2.nonEmpty) {
                    relax(node, pq2.dequeue.node)
                    loop(pq2)
                  }
                }
                loop(ordNodes)

                node.visited = true
                if (doNodeVisitor)
                  if (isDefined(nodeVisitor)) nodeVisitor(node)
                  else {
                    nodeCnt += 1
                    extNodeVisitor(node, nodeCnt, 0, new DijkstraInformer[T] {
                      def queueIterator = qNodes.iterator
                      def costsIterator = dest.iterator
                    })
                  }
              }
              rec(pq)
            }
          }
          rec(qNodes)
          def traverseMapNodes(map: MMap[NodeT, NodeT]): Option[Path] =
            map
              .get(potentialSuccessor)
              .map(
                _ =>
                  new MinWeightEdgeLazyPath(
                    new MapPathTraversable[NodeT](map, potentialSuccessor, root),
                    subgraphEdges,
                    Edge.weightOrdering(weight))) orElse (
              if (root eq potentialSuccessor) Some(Path.zero(potentialSuccessor)) else None
            )
          traverseMapNodes(mapToPred)
        }

      protected[collection] def bfs(maybeHandle: Option[Handle] = None): Option[NodeT] =
        withHandle(maybeHandle) { implicit visitedHandle =>
          val untilDepth: Int = maxDepth
          var depth           = 0
          var nodeCnt         = 0
          import BfsInformer.Element
          val q                            = Queue[Element](Element(root, depth))
          @inline def nonVisited(n: NodeT) = !n.visited

          def visit(n: NodeT): Unit = {
            n.visited = true
            if (doNodeVisitor)
              if (isDefined(nodeVisitor)) nodeVisitor(n)
              else {
                nodeCnt += 1
                extNodeVisitor(n, nodeCnt, depth, new BfsInformer {
                  def queueIterator = q.iterator
                })
              }
          }

          visit(root)
          while (q.nonEmpty) {
            val Element(prevNode, prevDepth, cumWeight) = q.dequeue
            if (prevDepth < untilDepth) {
              depth = prevDepth + 1
              for (n <- filteredNodes(prevNode, nonVisited, cumWeight, false)) {
                visit(n)
                if (stopAt(n, nodeCnt, depth)) return Some(n)
                q enqueue Element(
                  n,
                  depth,
                  maxWeight.fold(ifEmpty = 0d)(w => cumWeight + minWeight(prevNode, n, cumWeight))
                )
              }
            }
          }
          None
        }

      @inline protected[collection] def dfs[U](maybeHandle: Option[Handle] = None): Option[NodeT] =
        dfsStack(Visitor.empty, maybeHandle)._1

      /** @return (node stopped at, stack of ...) */
      protected[collection] def dfsStack[U](
          nodeUpVisitor: (NodeT) => U = Visitor.empty,
          maybeHandle: Option[Handle] = None): (Option[NodeT], Stack[DfsInformer.Element]) =
        withHandle(maybeHandle) { implicit visitedHandle =>
          val untilDepth: Int = maxDepth
          val doNodeUpVisitor = isDefined(nodeUpVisitor)

          @inline def nonVisited(n: NodeT): Boolean = !n.visited
          import DfsInformer.Element
          val stack: Stack[Element] = Stack(Element(root, 0))
          val path: Stack[Element]  = Stack()
          var res: Option[NodeT]    = None
          var nodeCnt               = 0
          @tailrec def loop {
            if (stack.nonEmpty) {
              val popped @ Element(current, depth, cumWeight) = stack.pop
              if (depth > 0)
                while (path.head.depth >= depth) {
                  if (doNodeUpVisitor) nodeUpVisitor(path.head.node)
                  path.pop
                }
              if (current.visited) {
                if (depth < untilDepth) {
                  val nextDepth = depth + 1
                  for (n <- filteredNodes(current, nonVisited, cumWeight, true)) {
                    stack push Element(
                      n,
                      nextDepth,
                      maxWeight.fold(ifEmpty = 0d)(_ => cumWeight + minWeight(current, n, cumWeight))
                    )
                  }
                }
                loop
              } else {
                current.visited = true
                path.push(popped)
                if (doNodeVisitor)
                  if (isDefined(nodeVisitor)) nodeVisitor(current)
                  else {
                    nodeCnt += 1
                    extNodeVisitor(current, nodeCnt, depth, new DfsInformer {
                      def stackIterator = stack.iterator
                      def pathIterator  = path.iterator
                    })
                  }
                if (stopAt(current, nodeCnt, depth) && (current ne root)) {
                  res = Some(current)
                } else {
                  if (depth < untilDepth) {
                    val nextDepth = depth + 1
                    for (n <- filteredNodes(current, nonVisited, cumWeight, true)) {
                      stack push Element(
                        n,
                        nextDepth,
                        maxWeight.fold(ifEmpty = 0d)(_ => cumWeight + minWeight(current, n, cumWeight))
                      )
                    }
                  }
                  loop
                }
              }
            }
          }
          loop
          if (doNodeUpVisitor) path foreach (e => nodeUpVisitor(e.node))
          (res, path)
        }

      protected[collection] def dfsTarjan(maybeHandle: Option[Handle] = None,
                                          nodeUpVisitor: (NodeT) => U = Visitor.empty): Iterable[Component] =
        withHandle(maybeHandle) { implicit visitedHandle =>
          type Element = TarjanInformer.Element
          val Element = TarjanInformer.Element

          val untilDepth: Int = maxDepth
          val doNodeUpVisitor = isDefined(nodeUpVisitor)

          var maxIndex   = 0
          val stack      = Stack.empty[Element]
          val onStack    = new EqHashMap[NodeT, Element](order / expectedMaxNodes(2))
          var components = List.empty[Component]

          // replaces successor loop in tail recursion
          object Level {
            private val stack: Stack[Level] = Stack.empty[Level]
            def push(level: Level): Level   = { stack push level; level }
            def pop: Level                  = stack.pop
            def stackHead: Level            = stack.head
            def nonEmptyStack: Boolean      = stack.nonEmpty
          }
          case class Level(elem: Element, successors: Iterator[NodeT]) {
            def this(elem: Element) = this(
              elem,
              if (elem.depth < untilDepth) filteredSuccessors(elem.node, anyNode, elem.cumWeight, false).toIterator
              else Iterator.empty)
          }
          sealed trait Phase
          case object Down             extends Phase
          case object Loop             extends Phase
          case class Up(from: Element) extends Phase

          @tailrec def loop(level: Level, phase: Phase): Unit = {
            val Level(elem @ Element(current, depth, cumWeight, index, _), successors) = level
            (phase, successors) match {
              case (Down, _) =>
                stack push elem
                onStack.put(current, elem)
                current.visited = true
                if (doNodeVisitor)
                  if (isDefined(nodeVisitor)) nodeVisitor(current)
                  else
                    extNodeVisitor(current, index, depth, new TarjanInformer(index, elem.lowLink) {
                      def stackIterator = stack.iterator
                    })
                loop(level, Loop)
              case (Loop, successors) if successors.hasNext =>
                val w = successors.next
                if (w.visited) {
                  onStack.get(w) map (wElem => elem.lowLink = math.min(elem.lowLink, wElem.index))
                  loop(level, Loop)
                } else {
                  maxIndex += 1
                  loop(
                    Level.push(
                      new Level(
                        Element(
                          w,
                          depth + 1,
                          maxWeight.fold(ifEmpty = 0d)(_ => cumWeight + minWeight(current, w, cumWeight)),
                          maxIndex,
                          maxIndex))),
                    Down)
                }
              case (Loop, _) =>
                if (elem.lowLink == index) {
                  val componentNodes = Set.newBuilder[NodeT]
                  @tailrec def pop(continue: Boolean): Unit = if (continue) {
                    val n = stack.pop.node
                    onStack.remove(n)
                    componentNodes += n
                    pop(n ne current)
                  }
                  pop(true)
                  components = new StrongComponentImpl(
                    current,
                    parameters,
                    subgraphNodes,
                    subgraphEdges,
                    ordering,
                    componentNodes.result) +: components
                }
                if (doNodeUpVisitor) nodeUpVisitor(current)
                val pred = Level.pop.elem
                if (Level.nonEmptyStack) loop(Level.stackHead, Up(pred))
              case (Up(wElem), _) =>
                elem.lowLink = math.min(elem.lowLink, wElem.lowLink)
                loop(level, Loop)
            }
          }

          loop(Level.push(new Level(Element(root, 0))), Down)
          components
        }

      /** Tail-recursive white-gray-black DFS implementation for cycle detection.
        */
      protected[collection] def dfsWGB(globalState: Array[Handle] = Array.empty[Handle],
                                       mustContain: Option[NodeT] = None): Option[(NodeT, Stack[CycleStackElem])] = {
        withHandles(2, globalState) { handles =>
          import WgbInformer.Element

          implicit val visitedHandle: Handle = handles(0)
          val blackHandle                    = handles(1)

          val isDiGraph    = thisGraph.isDirected
          val isMixedGraph = thisGraph.isMixed

          def isWhite(node: NodeT)  = nonVisited(node)
          def isGray(node: NodeT)   = isVisited(node) && !(node bit blackHandle)
          def isBlack(node: NodeT)  = node bit blackHandle
          def nonBlack(node: NodeT) = !isBlack(node)
          def setGray(node: NodeT) { node.visited = true }
          def setBlack(node: NodeT) { node.bit_=(isSet = true)(blackHandle) }

          def onNodeDown(node: NodeT) { setGray(node) }
          def onNodeUp(node: NodeT) { setBlack(node) }

          def isVisited(node: NodeT)  = node.visited
          def nonVisited(node: NodeT) = !isVisited(node)

          var nodeCnt = 0
          @tailrec def loop(pushed: Boolean,
                            stack: Stack[Element],
                            path: Stack[CycleStackElem]): Option[(NodeT, Stack[CycleStackElem])] =
            if (stack.isEmpty) {
              path foreach (t => setBlack(t.node))
              None
            } else {
              val Element(current, poppedPredecessor, poppedExclude, poppedMultiEdges, cumWeight) = stack.pop
              if (!pushed)
                while (path.nonEmpty &&
                       (path.head.node ne root) &&
                       (path.head.node ne poppedPredecessor)) {
                  val p = path.pop().node
                  if (nonBlack(p))
                    onNodeUp(p)
                }
              val exclude: Option[NodeT] = if (poppedExclude) Some(poppedPredecessor) else None
              path.push(new CycleStackElem(current, poppedMultiEdges))
              if (nonVisited(current)) onNodeDown(current)
              if (doNodeVisitor)
                if (isDefined(nodeVisitor)) nodeVisitor(current)
                else {
                  nodeCnt += 1
                  extNodeVisitor(current, nodeCnt, 0, new WgbInformer {
                    def stackIterator: Iterator[Element]       = stack.iterator
                    def pathIterator: Iterator[CycleStackElem] = path.iterator
                  })
                }

              def mixedCycle(blackSuccessors: Iterable[NodeT]): Option[(NodeT, Stack[CycleStackElem])] =
                withHandle() { handle =>
                  val visitedBlackHandle = Some(handle)
                  for (n <- blackSuccessors) {
                    thisImpl
                      .withRoot(n)
                      .withSubgraph(n => subgraphNodes(n) && !isWhite(n) && (n ne current), subgraphEdges)
                      .pathUntil_(isGray, maybeHandle = visitedBlackHandle)
                      .foreach { missingPath =>
                        val start = missingPath.endNode
                        val shortenedPath = {
                          var found = false
                          path takeWhile {
                            case CycleStackElem(n, _) =>
                              if (n eq start) {
                                found = true
                                true
                              } else !found
                          }
                        }
                        if (mustContain.forall { n =>
                              missingPath.nodes.exists(_ eq n) ||
                              shortenedPath.exists { case CycleStackElem(pn, _) => pn eq n }
                            }) {
                          ((missingPath.nodes.head connectionsWith shortenedPath.head.node).head /: missingPath) {
                            case (edge, InnerNode(n)) =>
                              if (isBlack(n))
                                shortenedPath.push(new CycleStackElem(n, Set(edge)))
                              edge
                            case (_, InnerEdge(edge)) => edge
                          }
                          return Some(start, shortenedPath)
                        }
                      }
                  }
                  None
                }

              def cycle(graySuccessors: Iterable[NodeT]): Option[(NodeT, Stack[CycleStackElem])] =
                graySuccessors find { n =>
                  val relevantPathContainsRequiredNode: Boolean = mustContain forall { req =>
                    (n eq req) || path.iterator.takeWhile { case CycleStackElem(sn, _) => sn ne n }.exists {
                      case CycleStackElem(sn, _) => sn eq req
                    }
                  }
                  exclude.fold(ifEmpty = true)(_ ne n) && relevantPathContainsRequiredNode
                } map ((_, path))

              if (current.hook.isDefined && mustContain.forall(_ eq current))
                Some(current, path)
              else {
                import TraverserImpl._
                def color(n: NodeT): Wgb =
                  if (isBlack(n)) black
                  else if (isGray(n)) gray
                  else white

                val successorsByColor: Map[Wgb, Iterable[NodeT]] = filteredSuccessors(
                  current,
                  if (isMixedGraph) anyNode else nonBlack,
                  cumWeight,
                  reverse = true
                ) groupBy color withDefaultValue Nil

                mixedCycle(successorsByColor(black)) orElse
                  cycle(successorsByColor(gray)) match {

                  case result @ Some(_) => result
                  case None =>
                    successorsByColor(white) match {
                      case whiteSuccessors if whiteSuccessors.nonEmpty =>
                        for (n <- whiteSuccessors) {
                          val newCumWeight =
                            maxWeight.fold(ifEmpty = 0d)(w => cumWeight + minWeight(current, n, cumWeight))
                          if (isDiGraph)
                            stack.push(Element(n, current, exclude = false, Nil, newCumWeight))
                          else
                            current connectionsWith n match {
                              case conn =>
                                ((conn.size: @switch) match {
                                  case 0 => throw new NoSuchElementException
                                  case 1 => (true, conn)
                                  case _ => (false, conn)
                                }) match {
                                  case (excl, multi) => stack.push(Element(n, current, excl, multi, newCumWeight))
                                }
                            }
                        }
                        loop(true, stack, path)
                      case _ =>
                        loop(false, stack, path)
                    }
                }
              }
            }

          loop(true, Stack(Element(root, root, exclude = false, Nil)), Stack.empty[CycleStackElem])
        }
      }

      protected[collection] def topologicalSort(setup: TopoSortSetup,
                                                maybeHandle: Option[Handle] = None): CycleNodeOrTopologicalOrder =
        withHandle(maybeHandle) { implicit handle =>
          def nonVisited(node: NodeT) = !node.visited
          val (
            layer_0: Iterable[NodeT],
            inDegrees: MMap[NodeT, Int],
            maybeInspectedNode: Option[NodeT]
          )                                   = setup
          val untilDepth: Int                 = maxDepth
          val estimatedLayers: Int            = expectedMaxNodes(4)
          val estimatedNodesPerLayer: Int     = order / estimatedLayers
          val layers                          = new ArrayBuffer[Layer](estimatedLayers)
          val maybeCycleNodes                 = MSet.empty[NodeT]
          def emptyBuffer: ArrayBuffer[NodeT] = new ArrayBuffer[NodeT](estimatedNodesPerLayer)

          @tailrec def loop(layer: Int, layerNodes: ArrayBuffer[NodeT]): CycleNodeOrTopologicalOrder = {
            layers += Layer(layer, layerNodes)

            val currentLayerNodes = if (doNodeSort) layerNodes.sorted(nodeOrdering) else layerNodes
            val nextLayerNodes    = emptyBuffer

            val nrEnqueued = (0 /: currentLayerNodes) { (sum, node) =>
              if (doNodeVisitor) nodeVisitor(node)
              node.visited = true
              (0 /: filteredSuccessors(node, nonVisited, Double.NaN, reverse = true)) { (zeroInDegreeCount, n) =>
                val newInDegree = inDegrees(n) - 1
                inDegrees.update(n, newInDegree)
                if (newInDegree == 0) {
                  nextLayerNodes += n
                  maybeCycleNodes -= n
                  zeroInDegreeCount + 1
                } else {
                  maybeCycleNodes += n
                  zeroInDegreeCount
                }
              } + sum
            }

            if (nrEnqueued == 0 || layers.size == untilDepth)
              maybeCycleNodes.headOption.fold[CycleNodeOrTopologicalOrder](
                Right(new TopologicalOrder(layers, identity))
              )(Left(_))
            else
              loop(layer + 1, nextLayerNodes)
          }

          maybeInspectedNode match {
            case Some(inspectedNode) if layer_0.isEmpty => Left(inspectedNode)
            case _ =>
              val startBuffer = layer_0 match {
                case b: ArrayBuffer[NodeT] => b
                case t                     => emptyBuffer ++ t
              }
              loop(0, startBuffer)
          }
        }
    }

    protected[collection] object Runner {
      @inline def apply[U](stopAt: NodeFilter, visitor: A => U): Runner[U]    = apply(StopCondition(stopAt), visitor)
      @inline def apply[U](stopAt: StopCondition, visitor: A => U): Runner[U] = new Runner[U](stopAt, visitor)
    }

    abstract protected class StopCondition extends ((NodeT, Int, Int) => Boolean) {
      def apply(n: NodeT, count: Int, depth: Int): Boolean
    }
    protected object StopCondition {
      def apply(nodePredicate: NodeT => Boolean): StopCondition = new StopCondition {
        def apply(n: NodeT, count: Int, depth: Int): Boolean = nodePredicate(n)
      }
      lazy val None: StopCondition = StopCondition(noNode)
    }
  }
}

private object TraverserImpl {
  sealed trait Wgb
  object white extends Wgb
  object gray  extends Wgb
  object black extends Wgb
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy