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

scalax.collection.constrained.constraints.Acyclic.scala Maven / Gradle / Ivy

The newest version!
package scalax.collection.constrained
package constraints

import scala.annotation.unchecked.{uncheckedVariance => uV}
import scala.language.{higherKinds, postfixOps}
import scala.collection.Set
import scala.collection.mutable.{Set => MutableSet}

import scalax.collection.GraphPredef._
import scalax.collection.{Graph => SimpleGraph}
import PreCheckFollowUp._
import scalax.collection.config.CoreConfig

/** Ensures that the underlying `Graph` is acyclic at any time. */
class Acyclic[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]](override val self: G) extends Constraint[N, E, G](self) {
  override def preCreate(nodes: Traversable[N], edges: Traversable[E[N]]): PreCheckResult =
    PreCheckResult.postCheck(edges forall (_.nonLooping))

  /** Adding a single node cannot produce a cycle. */
  override def preAdd(node: N) = PreCheckResult(Complete)

  /** When inserting an edge with a source contained in the graph a cycle is produced
    * if there exists a target node of this edge such that there is a path from the target node to the source node.
    */
  def preAdd(edge: E[N]): PreCheckResult = PreCheckResult.complete(
    edge.nonLooping && {
      val isUndirected       = edge.isUndirected
      val checkFrom, checkTo = MutableSet.empty[self.NodeT]
      def find(n: N)         = self find n
      def add1(n: N) {
        find(n) map { inner =>
          checkFrom += inner
          if (isUndirected) checkTo += inner
        }
      }
      def add2(toSet: MutableSet[self.NodeT], n: N, nUndi: N) {
        find(n) map (inner => toSet += inner)
        find(nUndi) map { inner =>
          if (isUndirected) toSet += inner
        }
      }
      add2(checkTo, edge._1, edge._2)
      add2(checkFrom, edge._2, edge._1)
      for (n <- edge.iterator.drop(2))
        add1(n)

      !((for {
        from <- checkFrom
        to   <- checkTo
      } yield (from, to)) exists (t => t._1 hasSuccessor t._2))
    }
  )

  /** This class makes it possible to carry over nodes that are passed to a pre-check and
    *  are already contained in the graph to `postAdd`. Thus we can limit the number of start
    *  nodes for cycle detection in the post-check to these passed over docking nodes.
    */
  protected class Result(followUp: PreCheckFollowUp, val dockingNodes: Set[self.NodeT]) extends PreCheckResult(followUp)

  protected object Result extends PreCheckResultCompanion {
    def apply(followUp: PreCheckFollowUp)                                = new Result(followUp, Set.empty[self.NodeT])
    def apply(followUp: PreCheckFollowUp, nodesToCheck: Set[self.NodeT]) = new Result(followUp, nodesToCheck)
    def unapply(result: Result): Option[Set[self.NodeT]]                 = Some(result.dockingNodes)
  }

  /** If `elems` is relatively small, preventively ensure that there is no cycle within the new elements.
    */
  override def preAdd(elems: InParam[N, E]*): PreCheckResult =
    if (elems.size * 10 < self.size) {
      val p = Param.Partitions(elems)
      val graphAdd =
        SimpleGraph.from(p.toOuterNodes, p.toOuterEdges)(
          self.edgeT,
          CoreConfig(self.config.orderHint, self.config.adjacencyListHints))
      if (graphAdd.isCyclic)
        PreCheckResult(Abort)
      else {
        val found = (for (n <- p.toOuterNodes) yield self find n) filter (_.isDefined)
        Result(PostCheck, (found map (_.get)) toSet)
      }
    } else PreCheckResult(PostCheck)

  override def postAdd(newGraph: G @uV,
                       passedNodes: Traversable[N],
                       passedEdges: Traversable[E[N]],
                       preCheck: PreCheckResult): Either[PostCheckFailure, G] = {
    def msg(at: Option[self.NodeT]) =
      s"Unexpected cycle ${at.fold("")("at " + _)}when adding $passedNodes, $passedEdges."
    preCheck match {
      case Result(docking) =>
        docking find (_.findCycle.isDefined) match {
          case Some(node) => Left(PostCheckFailure(msg(Some(node))))
          case None       => Right(newGraph)
        }
      case _ =>
        if (newGraph.isAcyclic) Right(newGraph)
        else Left(PostCheckFailure(msg(None)))
    }
  }

  override def preSubtract(node: self.NodeT, forced: Boolean) = PreCheckResult(Complete)
  override def preSubtract(edge: self.EdgeT, forced: Boolean) = PreCheckResult(Complete)
  override def preSubtract(nodes: => Set[self.NodeT], edges: => Set[self.EdgeT], simple: Boolean): PreCheckResult =
    PreCheckResult(Complete)
}
object Acyclic extends ConstraintCompanion[Acyclic] {
  def apply[N, E[X] <: EdgeLikeIn[X], G <: Graph[N, E]](self: G) = new Acyclic[N, E, G](self)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy