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

izumi.fundamentals.graphs.tools.Toposort.scala Maven / Gradle / Ivy

The newest version!
package izumi.fundamentals.graphs.tools

import izumi.fundamentals.graphs.ToposortError
import izumi.fundamentals.graphs.ToposortError.InconsistentInput
import izumi.fundamentals.graphs.struct.IncidenceMatrix

import scala.annotation.{nowarn, tailrec}

object Toposort {

  final def cycleBreaking[T](predecessors: IncidenceMatrix[T], break: ToposortLoopBreaker[T]): Either[ToposortError[T], Seq[T]] = {
    cycleBreaking(predecessors.links, Seq.empty, break)
  }

  @nowarn("msg=Unused import")
  @tailrec
  private def cycleBreaking[T](predecessors: Map[T, Set[T]], done: Seq[T], break: ToposortLoopBreaker[T]): Either[ToposortError[T], Seq[T]] = {
    import scala.collection.compat._
    val (noPreds, hasPreds) = predecessors.partition(_._2.isEmpty)

    if (noPreds.isEmpty) {
      if (hasPreds.isEmpty) {
        Right(done)
      } else { // circular dependency
        val maybeNext = for {
          loopMembers <- Right(hasPreds.view.filterKeys(isInvolvedIntoCycle(hasPreds)).toMap)
          _ <- if (loopMembers.isEmpty) Left(InconsistentInput(IncidenceMatrix(hasPreds))) else Right(())
          resolved <- break.onLoop(done, loopMembers)
          next = hasPreds.view.filterKeys(k => !resolved.breakAt.contains(k)).mapValues(_ -- resolved.breakAt).toMap

        } yield {
          (resolved.breakAt, next)
        }

        maybeNext match {
          case Right((breakAt, next)) =>
            cycleBreaking(next, done ++ breakAt, break)

          case Left(e) =>
            Left(e)
        }
      }
    } else {
      val found = noPreds.keySet
      val next = hasPreds.view.mapValues(_ -- found).toMap
      cycleBreaking(next, done ++ found, break)
    }
  }

  private def isInvolvedIntoCycle[T](toPreds: Map[T, Set[T]])(key: T): Boolean = {
    test(toPreds, Set.empty, key, key)
  }

  private def test[T](toPreds: Map[T, Set[T]], stack: Set[T], toTest: T, needle: T): Boolean = {
    val deps = toPreds.getOrElse(toTest, Set.empty)

    if (deps.contains(needle)) {
      true
    } else {
      deps.exists {
        d =>
          if (stack.contains(d)) {
            false
          } else {
            test(toPreds, stack + d, d, needle)
          }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy