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

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

The newest version!
package scalax.collection

import scala.collection.generic.CanBuildFrom
import scala.reflect.ClassTag

import GraphPredef.{EdgeLikeIn, InnerEdgeParam, InnerNodeParam, OuterEdge, OuterNode, Param}
import GraphEdge.{DiEdge, DiHyperEdgeLike, Keyed, UnDiEdge}
import generic.GraphCoreCompanion
import config.GraphConfig

/** A template trait for graphs.
  *
  * This trait provides the common structure and operations of immutable graphs independently
  * of their representation.
  *
  * If `E` inherits `DiHyperEdgeLike` the graph is directed, otherwise it is undirected or mixed.
  *
  * @tparam N    the user type of the nodes (vertices) in this graph.
  * @tparam E    the higher kinded type of the edges (links) in this graph.
  * @tparam This the higher kinded type of the graph itself.
  *
  * @define REIMPLFACTORY Note that this method must be reimplemented in each module
  *         having its own factory methods such as `constrained` does.
  * @define CONTGRAPH The `Graph` instance that contains `this`
  * @author Peter Empen
  */
trait GraphLike[
    N,
    E[+X] <: EdgeLikeIn[X],
    +This[NN, EE[+XX] <: EdgeLikeIn[XX]] <: GraphLike[NN, EE, This] with AnySet[Param[NN, EE]] with Graph[NN, EE]]
    extends GraphAsSet[N, E, This]
    with GraphTraversal[N, E]
    with GraphBase[N, E]
    with GraphDegree[N, E] {
  //thisGraph: This[N,E] => // see https://youtrack.jetbrains.com/issue/SCL-13199
  thisGraph: This[N, E] with GraphLike[N, E, This] with AnySet[Param[N, E]] with Graph[N, E] =>

  protected type ThisGraph = thisGraph.type
  implicit val edgeT: ClassTag[E[N]]

  def isDirected: Boolean         = isDirectedT || edges.hasOnlyDiEdges
  final protected val isDirectedT = classOf[DiHyperEdgeLike[_]].isAssignableFrom(edgeT.runtimeClass)

  def isHyper: Boolean         = isHyperT && edges.hasAnyHyperEdge
  final protected val isHyperT = !classOf[UnDiEdge[_]].isAssignableFrom(edgeT.runtimeClass)

  def isMixed: Boolean = !isDirectedT && edges.hasMixedEdges

  def isMulti: Boolean         = isMultiT || edges.hasAnyMultiEdge
  final protected val isMultiT = classOf[Keyed].isAssignableFrom(edgeT.runtimeClass)

  protected type Config <: GraphConfig
  implicit def config: graphCompanion.Config with Config

  override def stringPrefix: String = "Graph"

  /** Ensures sorted nodes/edges unless this `Graph` has more than 100 elements.
    * See also `asSortedString` and `toSortedString`.
    */
  override def toString = if (size <= 100) toSortedString()()
  else super.toString

  /** Sorts all nodes of this graph by `ordNode` followed by all edges sorted by `ordEdge`
    * and concatinates their string representation `nodeSeparator` and `edgeSeparator`
    * respectively.
    *
    * @param nodeSeparator to separate nodes by.
    * @param edgeSeparator to separate edges by.
    * @param nodesEdgesSeparator to separate nodes from edges by.
    * @param withNodesEdgesPrefix whether the node and edge set should be prefixed.
    * @param ordNode the node ordering defaulting to `defaultNodeOrdering`.
    * @param ordEdge the edge ordering defaulting to `defaultEdgeOrdering`.
    */
  def asSortedString(nodeSeparator: String = GraphBase.defaultSeparator,
                     edgeSeparator: String = GraphBase.defaultSeparator,
                     nodesEdgesSeparator: String = GraphBase.defaultSeparator,
                     withNodesEdgesPrefix: Boolean = false)(implicit ordNode: NodeOrdering = defaultNodeOrdering,
                                                            ordEdge: EdgeOrdering = defaultEdgeOrdering) = {
    val ns =
      if (withNodesEdgesPrefix) nodes.toSortedString(nodeSeparator)(ordNode)
      else nodes.asSortedString(nodeSeparator)(ordNode)
    val es =
      if (withNodesEdgesPrefix) edges.toSortedString(edgeSeparator)(ordEdge)
      else edges.asSortedString(edgeSeparator)(ordEdge)
    ns + (if (ns.length > 0 && es.length > 0) nodesEdgesSeparator
          else "") +
    es
  }

  /** Same as `asSortedString` but additionally prefixed and parenthesized by `stringPrefix`.
    */
  def toSortedString(nodeSeparator: String = GraphBase.defaultSeparator,
                     edgeSeparator: String = GraphBase.defaultSeparator,
                     nodesEdgesSeparator: String = GraphBase.defaultSeparator,
                     withNodesEdgesPrefix: Boolean = false)(implicit ordNode: NodeOrdering = defaultNodeOrdering,
                                                            ordEdge: EdgeOrdering = defaultEdgeOrdering) =
    stringPrefix +
      "(" + asSortedString(nodeSeparator, edgeSeparator, nodesEdgesSeparator, withNodesEdgesPrefix)(ordNode, ordEdge) +
      ")"

  /** `Graph` instances are equal if their nodes and edges turned
    * to outer nodes and outer edges are equal. Any `TraversableOnce`
    * instance may also be equal to this graph if its set representation
    * contains equalling outer nodes and outer edges. Thus the following
    * expressions hold:
    * {{{
    * Graph(1~2, 3) == List(1~2, 3)
    * Graph(1~2, 3) == List(1, 2, 2, 3, 2~1)
    * }}}
    * The first test is `false` because of the failing nodes `1` and `2`.
    * The second is true because of duplicate elimination and undirected edge equivalence.
    */
  override def equals(that: Any): Boolean = that match {
    case that: Graph[N, E] =>
      (this eq that) ||
        (this.order == that.order) &&
          (this.graphSize == that.graphSize) && {
          val thatNodes = that.nodes.toOuter
          try this.nodes forall (thisN => thatNodes(thisN.value))
          catch { case _: ClassCastException => false }
        } && {
          val thatEdges = that.edges.toOuter
          try this.edges forall (thisE => thatEdges(thisE.toOuter))
          catch { case _: ClassCastException => false }
        }
    case that: TraversableOnce[_] =>
      val thatSet = that.toSet
      (this.size == thatSet.size) && {
        val thatNodes = thatSet.asInstanceOf[Set[N]]
        try this.nodes forall (thisN => thatNodes(thisN.value))
        catch { case _: ClassCastException => false }
      } && {
        val thatEdges = thatSet.asInstanceOf[Set[E[N]]]
        try this.edges forall (thisE => thatEdges(thisE.toOuter))
        catch { case _: ClassCastException => false }
      }
    case _ =>
      false
  }

  type NodeT <: InnerNode
  trait InnerNode extends super.InnerNode with TraverserInnerNode {
    this: NodeT =>

    /** $CONTGRAPH inner edge. */
    final def containingGraph: ThisGraph = thisGraph
  }
  abstract protected class NodeBase(override val value: N)
      extends super.NodeBase
      with InnerNodeParam[N]
      with InnerNode {
    this: NodeT =>
    final def isContaining[N, E[+X] <: EdgeLikeIn[X]](g: GraphBase[N, E]): Boolean =
      g eq containingGraph
  }

  type NodeSetT <: NodeSet
  trait NodeSet extends super.NodeSet {
    protected def copy: NodeSetT

    final override def -(node: NodeT): NodeSetT =
      if (this contains node) { val c = copy; c minus node; c } else this.asInstanceOf[NodeSetT]

    /** removes `node` from this node set leaving the edge set unchanged.
      *
      * @param node the node to be removed from the node set.
      */
    protected def minus(node: NodeT): Unit

    /** removes `node` either rippling or gently.
      *
      * @param node the node to be subtracted
      * @param rippleDelete if `true`, `node` will be deleted with its incident edges;
      *        otherwise `node` will be only deleted if it has no incident edges or
      *        all its incident edges are hooks.
      * @param minusNode implementation of node removal without considering incident edges.
      * @param minusEdges implementation of removal of all incident edges.
      * @return `true` if `node` has been removed.
      */
    final protected[collection] def subtract(node: NodeT,
                                             rippleDelete: Boolean,
                                             minusNode: (NodeT) => Unit,
                                             minusEdges: (NodeT) => Unit): Boolean = {
      def minusNodeTrue = { minusNode(node); true }
      def minusAllTrue  = { minusEdges(node); minusNodeTrue }
      if (contains(node))
        if (node.edges.isEmpty) minusNodeTrue
        else if (rippleDelete) minusAllTrue
        else if (node.hasOnlyHooks) minusAllTrue
        else handleNotGentlyRemovable
      else false
    }
    protected def handleNotGentlyRemovable = false
  }

  trait InnerEdge extends super.InnerEdge {
    this: EdgeT =>

    /** $CONTGRAPH inner edge. */
    final def containingGraph: ThisGraph = thisGraph
  }
  type EdgeT <: InnerEdgeParam[N, E, NodeT, E] with InnerEdge with Serializable
  class EdgeBase(override val edge: E[NodeT]) extends InnerEdgeParam[N, E, NodeT, E] with InnerEdge {
    this: EdgeT =>
    override def iterator: Iterator[NodeT] = edge.iterator
    override def stringPrefix              = super.stringPrefix
  }
  type EdgeSetT <: EdgeSet
  trait EdgeSet extends super.EdgeSet {
    def hasOnlyDiEdges: Boolean
    def hasOnlyUnDiEdges: Boolean
    def hasMixedEdges: Boolean
    def hasAnyHyperEdge: Boolean
    def hasAnyMultiEdge: Boolean
  }

  /** Checks whether a given node or edge is contained in this graph.
    *
    *  @param elem the node or edge the existence of which is to be checked
    *  @return true if `elem` is contained in this graph
    */
  def contains(elem: Param[N, E]) = elem match {
    case n: OuterNode[N]    => nodes contains newNode(n.value)
    case e: OuterEdge[N, E] => edges contains newEdge(e.edge)
    case n: InnerNodeParam[N] =>
      val node = n.toNodeT[N, E, ThisGraph](thisGraph)(anyNode => newNode(anyNode.value))
      nodes contains node
    case e: InnerEdgeParam[N, E, _, E] =>
      val edge = e.toEdgeT[N, E, ThisGraph](thisGraph)(anyEdge => newEdge(anyEdge.toOuter))
      edges contains edge
  }

  /** Iterates over all nodes and all edges.
    *
    *  @return iterator containing all nodes and all edges
    */
  def iterator: Iterator[Param[N, E]] = nodes.iterator ++ edges.iterator

  /** Searches for an inner node equaling to `outerNode` in this graph.
    *
    * @param outerNode the outer node to search for in this graph.
    * @return `Some` of the inner node found or `None`.
    */
  @inline final def find(outerNode: N): Option[NodeT] = nodes find outerNode

  /** Searches for an edge node equaling to `outerEdge` in this graph.
    *
    * @param outerEdge the outer edge to search for in this graph.
    * @return `Some` of the inner edge found or `None`.
    */
  @inline final def find(outerEdge: E[N]): Option[EdgeT] = edges find outerEdge

  /** Searches for an inner node equaling to `outerNode` in this graph
    * which must exist in this graph.
    *
    * @param outerNode the outer node to search for in this graph.
    * @return the inner node if found. Otherwise NoSuchElementException is thrown.
    */
  @inline final def get(outerNode: N): NodeT = nodes get outerNode

  /** Searches for an inner edge equaling to `outerEdge` in this graph
    * which must exist in this graph.
    *
    * @param outerEdge the outer edge to search for in this graph.
    * @return the inner edge if found. Otherwise NoSuchElementException is thrown.
    */
  @inline final def get(outerEdge: E[N]) = find(outerEdge).get

  /** Searches for an inner node equaling to `outerNode` in this graph.
    *
    * @param outerNode the outer node to search for in this graph.
    * @param default the inner node to return if `outerNode` is not contained.
    * @return The inner node looked up or `default` if no inner node
    *         equaling to `outerNode` could be found.
    */
  @inline final def getOrElse(outerNode: N, default: NodeT) = find(outerNode).getOrElse(default)

  /** Searches for an inner edge equaling to `outerEdge` in this graph.
    *
    * @param outerEdge the outer edge to search for in this graph.
    * @param default the inner edge to return if `outerEdge` cannot be found.
    * @return the inner edge looked up or `default` if no inner edge
    *         equaling to `edge` could be found.
    */
  @inline final def getOrElse(outerEdge: E[N], default: EdgeT) =
    find(outerEdge).getOrElse(default)

  /** Creates a new supergraph with an additional node, unless the node passed is
    *  already present.
    *
    *  @param node the node to be added
    *  @return the new supergraph containing all nodes and edges of this graph and `node`
    *  additionally.
    */
  def +(node: N): This[N, E]

  /** Creates a new supergraph with an additional edge, unless the edge passed is
    *  already present.
    *
    *  @param edge the edge to be added
    *  @return the new supergraph containing all nodes and edges of this
    *          graph plus `edge`.
    */
  protected def +#(edge: E[N]): This[N, E]

  /** Creates a new supergraph with an additional node or edge, unless the
    *  node or edge passed is already present.
    *
    *  This method purely wraps `+(node: N)` respectively `+#(edge: E[N])`
    *  granting the same behavior.
    *
    *  @param elem the wrapped node or edge to be added; if `elem` is of type N,
    *         the wrapped object is added to the node set otherwise to the edge set.
    *  @return a new supergraph containing all nodes and edges of this graph
    *          plus `elem`.
    */
  def incl(elem: Param[N, E]): This[N, E] = elem match {
    case n: OuterNode[N]               => this + n.value
    case e: OuterEdge[N, E]            => this +# e.edge
    case n: InnerNodeParam[N]          => this + n.value
    case e: InnerEdgeParam[N, E, _, E] => this +# e.asEdgeT[N, E, ThisGraph](thisGraph).toOuter
  }

  /** Prepares and calls `plusPlus` or `minusMinus`. */
  final protected def bulkOp(elems: IterableOnce[Param[N, E]], isPlusPlus: Boolean): This[N, E] = {
    val p = partition(elems)
    if (isPlusPlus) plusPlus(p.toOuterNodes, p.toOuterEdges)
    else minusMinus(p.toOuterNodes, p.toOuterEdges)
  }

  final protected def partition(elems: IterableOnce[Param[N, E]]) =
    new Param.Partitions[N, E](elems match {
      case t: Iterable[Param[N, E]]     => t
      case g: IterableOnce[Param[N, E]] => g.toIterable
      case _                            => throw new MatchError("Iterable expected.")
    })

  /** Implements the heart of `++` calling the `from` factory method of the companion object.
    *  $REIMPLFACTORY */
  protected def plusPlus(newNodes: Iterable[N], newEdges: Iterable[E[N]]): This[N, E] =
    graphCompanion.from[N, E](nodes.toOuter ++ newNodes, edges.toOuter ++ newEdges)

  /** Implements the heart of `--` calling the `from` factory method of the companion object.
    *  $REIMPLFACTORY */
  protected def minusMinus(delNodes: Iterable[N], delEdges: Iterable[E[N]]): This[N, E] = {
    val delNodesEdges = minusMinusNodesEdges(delNodes, delEdges)
    graphCompanion.from[N, E](delNodesEdges._1, delNodesEdges._2)
  }

  /** Calculates the `nodes` and `edges` arguments to be passed to a factory method
    *  when delNodes and delEdges are to be deleted by `--`.
    */
  protected def minusMinusNodesEdges(delNodes: Iterable[N], delEdges: Iterable[E[N]]): (Set[N], Set[E[N]]) =
    (nodes.toOuter -- delNodes, {
      val delNodeSet = delNodes.toSet
      val restEdges =
        for (e <- edges.toOuter if e forall (n => !(delNodeSet contains n))) yield e
      restEdges -- delEdges
    })

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `node`
    *  and those edges which `node` is incident with.
    *
    *  @param node the node to be removed.
    *  @return the new subgraph of this graph after the "ripple" deletion of `node`.
    */
  def -(node: N): This[N, E]

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `node`
    *  which is conditionally removed from this graph. The removal only succeeds if the node
    *  is not incident with any edges or it is only incident with hooks.
    *
    *  @param node the node to be gently removed.
    *  @return the new subgraph of this graph after the "gentle" deletion of `node`.
    *          If `node` could not be deleted, the new graph is a copy of this graph.
    */
  @deprecated("To remove a node conditionally, check for the prerequisites beforehand.", "1.13")
  def minusIsolated(node: N): This[N, E]

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `edge`.
    * The node set remains unchanged.
    *
    *  @param edge the edge to be removed.
    *  @return a new subgraph of this graph that contains all nodes and edges of this graph
    *          except of `edge`.
    */
  protected def -#(edge: E[N]): This[N, E]

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `edge`
    *  and those nodes which are incident with `edge` and would become edge-less after deletion.
    *
    *  @param edge the edge to be removed.
    *  @return a new subgraph of this graph after the "ripple" deletion of `edge` from
    *          this graph.
    */
  protected def -!#(edge: E[N]): This[N, E]

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `elem`.
    *  If `elem` is of type N, this method maps to `-(node: N)`. Otherwise the edge is deleted
    *  leaving the node set unchanged.
    *
    *  @param elem node or edge to be removed.
    *  @return the new subgraph of this graph after the "ripple" deletion of the passed node
    *          or the simple deletion of the passed edge.
    */
  def excl(elem: Param[N, E]): This[N, E] = elem match {
    case n: OuterNode[N]               => this - n.value
    case e: OuterEdge[N, E]            => this -# e.edge
    case n: InnerNodeParam[N]          => this - n.value
    case e: InnerEdgeParam[N, E, _, E] => this -# e.asEdgeT[N, E, ThisGraph](thisGraph).toOuter
  }

  /** Creates a new subgraph consisting of all nodes and edges of this graph except `elem`.
    *  If `elem` is of type N, this method maps to `-(node: N)`. Otherwise the edge is deleted
    *  along with those incident nodes which would become isolated after deletion.
    *
    *  @param elem node or edge to be removed.
    *  @return a new subgraph of this graph after the "ripple" deletion of the passed
    *          node or edge.
    */
  @deprecated("When deleting an edge, to get nodes deleted that become isolated, delete them beforehand.", "1.13")
  def -!(elem: Param[N, E]): This[N, E] = elem match {
    case n: OuterNode[N]               => this - n.value
    case e: OuterEdge[N, E]            => this -!# e.edge
    case n: InnerNodeParam[N]          => this - n.value
    case e: InnerEdgeParam[N, E, _, E] => this -!# e.asEdgeT[N, E, ThisGraph](thisGraph).toOuter
  }

  /** Creates a new subgraph consisting of all nodes and edges of this graph but the elements
    * of `coll` which will be unconditionally removed. This operation differs from `--`
    * in that edges are deleted along with those incident nodes which would become isolated
    * after deletion.
    *
    * @param elems collection of nodes and/or edges to be removed; if the element type is N,
    *             it is removed from the node set otherwise from the edge set.
    *             See `-!(elem: Param[N,E])`.
    * @return the new subgraph containing all nodes and edges of this graph
    *         after the "ripple" deletion of nodes and the simple deletion of edges in `coll` .
    */
  @deprecated("When deleting edges, to get nodes deleted that become isolated, delete them beforehand.", "1.13")
  def --!(elems: IterableOnce[Param[N, E]]): This[N, E] = {
    val p                    = partition(elems)
    val (delNodes, delEdges) = (p.toOuterNodes, p.toOuterEdges)
    val unconnectedNodeCandidates = {
      val edgeNodes = MSet.empty[N]
      delEdges foreach (_ foreach (n => edgeNodes += n))
      edgeNodes -- delNodes
    }
    val delEdgeSet = {
      val edges = MSet.empty[EdgeT]
      delEdges foreach (this find _ map (edges += _))
      edges
    }
    minusMinus(
      delNodes ++
        (unconnectedNodeCandidates filter (nc => this find nc exists (n => n.edges forall (delEdgeSet contains _)))),
      delEdges)
  }

  /** Provides a shortcut for predicates involving any graph element.
    * In order to compute a subgraph of this graph, the result of this method
    * may be passed to any graph-level method requiring a predicate such as
    * `count`, `exists`, `filter`, `filterNot`, `forall` etc. For instance
    *
    * {{{
    * val g = Graph(2~>3, 3~>1, 5)
    * g filter g.having(nodes = _ >= 2) // yields Graph(2, 3, 5, 2~>3)
    * }}}
    *
    * @param node The predicate that must hold for the nodes.
    * @param edge The predicate that must hold for the edges. If omitted, all edges
    *             between nodes to be included by `nodes` will also be included.
    * @return A partial function combining the passed predicates.
    */
  def having(node: NodeFilter = _ => false, edge: EdgeFilter = null): PartialFunction[Param[N, E], Boolean] = {
    val nodePred: PartialFunction[Param[N, E], Boolean] = {
      case n: InnerNodeParam[N] => node(n.asNodeT[N, E, ThisGraph](thisGraph))
    }
    val edgePred: PartialFunction[Param[N, E], Boolean] =
      if (edge eq null) {
        case e: InnerEdgeParam[N, E, _, E] => e.asEdgeT[N, E, ThisGraph](thisGraph) forall (node(_))
      } else {
        case e: InnerEdgeParam[N, E, _, E] => edge(e.asEdgeT[N, E, ThisGraph](thisGraph))
      }
    nodePred orElse edgePred
  }
}

// ----------------------------------------------------------------------------

/** The main trait for immutable graphs bundling the functionality of traits concerned with
  * specific aspects.
  *
  * @tparam N the type of the nodes (vertices) in this graph.
  * @tparam E the kind of the edges in this graph.
  *
  * @author Peter Empen
  */
trait Graph[N, E[+X] <: EdgeLikeIn[X]] extends AnySet[Param[N, E]] with GraphLike[N, E, Graph] {
  override def empty: Graph[N, E] = Graph.empty[N, E]
  override def knownSize: Int     = nodes.size + edges.size
}

/** The main companion object for immutable graphs.
  *
  * @author Peter Empen
  */
object Graph extends GraphCoreCompanion[Graph] {
  override def newBuilder[N, E[+X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]], config: Config) =
    immutable.Graph.newBuilder[N, E](edgeT, config)
  def empty[N, E[+X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]], config: Config = defaultConfig): Graph[N, E] =
    immutable.Graph.empty[N, E](edgeT, config)
  def from[N, E[+X] <: EdgeLikeIn[X]](nodes: Iterable[N] = Nil, edges: Iterable[E[N]])(
      implicit edgeT: ClassTag[E[N]],
      config: Config = defaultConfig): Graph[N, E] =
    immutable.Graph.from[N, E](nodes, edges)(edgeT, config)

  implicit def cbfUnDi[N, E[+X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]], config: Config = defaultConfig) =
    new GraphCanBuildFrom[N, E]()(edgeT, config)
      .asInstanceOf[GraphCanBuildFrom[N, E] with CanBuildFrom[Graph[_, UnDiEdge], Param[N, E], Graph[N, E]]]

  implicit def cbfDi[N, E[+X] <: EdgeLikeIn[X]](implicit edgeT: ClassTag[E[N]], config: Config = defaultConfig) =
    new GraphCanBuildFrom[N, E]()(edgeT, config)
      .asInstanceOf[GraphCanBuildFrom[N, E] with CanBuildFrom[Graph[_, DiEdge], Param[N, E], Graph[N, E]]]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy