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

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

package scalax.collection

import scala.collection.{Iterable => AnyIterable}
import scala.language.implicitConversions
import scala.collection.{ExtSetMethods, FilterableSet}
import scala.util.Random
import scala.util.chaining._

import scalax.collection.generic._
import generic.AnyOrdering

/** Base template trait for graphs.
  *
  * This trait provides the common structure and base operations for immutable graphs
  * independently of their representation. Base operations also cover one-step traversals.
  * For unlimited traversals see `trait GraphTraversal`.
  *
  * Users of Graph usually don't interact directly with this trait but with
  * `trait Graph` instead which inherits the functionality provided by this trait.
  *
  * If `E` inherits `DirectedEdgeLike` 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 kind of the edges (links) in this graph.
  * @define INNODES The isolated (and optionally any other) outer nodes that the node set of
  *         this graph is to be populated with.
  * @define INEDGES The outer edges that the edge set of this graph is to be populated with.
  *         Nodes being the end of any of these edges will be added to the node set.
  * @author Peter Empen
  */
trait GraphBase[N, E <: Edge[N], +CC[X, Y <: Edge[X]] <: GraphBase[X, Y, CC]]
    extends GraphOps[N, E, CC]
    with OuterElems[N, E]
    with Serializable { selfGraph =>

  /** Populates this graph with `nodes` and `edges`.
    * The implementing class will typically have a constructor with the same parameters
    * which is invoked by `from` of the companion object.
    * @param nodes $INNODES
    * @param edges $INEDGES
    */
  protected def initialize(nodes: AnyIterable[N], edges: AnyIterable[E]): Unit = {
    this.nodes.initialize(nodes, edges)
    this.edges.initialize(edges)
  }

  /** The order - commonly referred to as |G| - of this graph equaling to the number of nodes. */
  final def order: Int = nodes.size

  /** The size - commonly referred to as |E| - of this graph equaling to the number of edges. */
  final def size: Int = edges.size

  // The following predicates must be val because eq does not work for def.
  /** Default node filter letting traverse all nodes (non-filter). */
  final val anyNode: NodePredicate = _ => true

  /** Node predicate always returning `false`. */
  final val noNode: NodePredicate = _ => false

  /** Default edge filter letting path all edges (non-filter). */
  final val anyEdge: EdgePredicate = _ => true

  /** Edge predicate always returning `false`. */
  final val noEdge: EdgePredicate = _ => false

  /** `true` if `f` is not equivalent to `anyNode`. */
  @inline final def isCustomNodeFilter(f: NodePredicate) = f ne anyNode

  /** `true` if `f` is not equivalent to `anyEdge`. */
  @inline final def isCustomEdgeFilter(f: EdgePredicate) = f ne anyEdge

  type NodeT <: BaseInnerNode with Serializable
  trait Node          extends Serializable
  trait BaseInnerNode extends Node with InnerNode {

    /** All edges at this node - commonly denoted as E(v).
      * @return all edges connecting to this node.
      */
    def edges: ExtSet[EdgeT]

    /** All edges connecting this node with `other` including outgoing and incoming edges.
      * This method is useful in case of multigraphs.
      * @param other A node which is possibly connected with this node.
      * @return All edges connecting this node with `other`.
      *         If `other` equals this node all hooks are returned.
      *         If `other` is not connected with this node an empty set is returned.
      */
    def connectionsWith(other: NodeT): Set[EdgeT] with FilterableSet[EdgeT]

    /** Checks whether this node has only hooks or no edges at all.
      *  @return `true` if this node has only hooks or it isolated.
      */
    def hasOnlyHooks: Boolean

    /** @return A looping edge out of one or more at this node or `None` if this node has no looping edge. */
    def hook: Option[EdgeT]

    /** Whether `that` is an adjacent (direct successor) to this node.
      * @param that The node to check for adjacency.
      * @return `true` if `that` is adjacent to this node.
      */
    def isDirectPredecessorOf(that: NodeT): Boolean

    /** Whether `that` is independent of this node meaning that
      * there exists no edge connecting this node with `that`.
      * @param that The node to check for independency.
      * @return `true` if `that` node is independent of this node.
      */
    def isIndependentOf(that: NodeT): Boolean

    /** All direct successors of this node, also called ''successor set'' or
      * ''open out-neighborhood'': target nodes of directed incident edges and / or
      * adjacent nodes of undirected incident edges excluding this node.
      * @return set of all direct successors of this node.
      */
    def diSuccessors: Set[NodeT]

    /** Whether this node has any successors. */
    def hasSuccessors: Boolean

    protected[collection] def addDiSuccessors(edge: EdgeT, add: NodeT => Unit): Unit

    /** Synonym for `diSuccessors`. */
    @inline final def outNeighbors: Set[NodeT] = diSuccessors

    /** All direct predecessors of this node, also called ''predecessor set'' or
      * ''open in-neighborhood'': source nodes of directed incident edges and / or
      * adjacent nodes of undirected incident edges excluding this node.
      * @return set of all direct predecessors of this node.
      */
    def diPredecessors: Set[NodeT]

    /** Whether this node has any predecessors. */
    def hasPredecessors: Boolean

    protected[collection] def addDiPredecessors(edge: EdgeT, add: NodeT => Unit): Unit

    /** Synonym for `diPredecessors`. */
    @inline final def inNeighbors = diPredecessors

    /** All adjacent nodes (direct successors and predecessors) of this node,
      * also called ''open neighborhood'' excluding this node.
      * @return set of all neighbors.
      */
    def neighbors: Set[NodeT]
    protected[collection] def addNeighbors(edge: EdgeT, add: NodeT => Unit): Unit

    /** All edges outgoing from this node.
      * @return set of all edges outgoing from this node
      *         including undirected edges and hooks.
      */
    def outgoing: Set[EdgeT] with FilterableSet[EdgeT]

    /** All outgoing edges connecting this node with `to`.
      * @param to The node which is the end point of zero, one or more edges starting at this node.
      * @return All edges connecting this node with `to`.
      *         If `to` equals this node all hooks are returned.
      *         If `to` is not an adjacent an empty set is returned.
      */
    def outgoingTo(to: NodeT): Set[EdgeT] with FilterableSet[EdgeT]

    /** An outgoing edge connecting this node with `to`.
      * @param to The node which is the end point of an edge starting at this node.
      * @return One of possibly several edges connecting this node with `to`.
      *         If `to` equals this node a hook may be returned.
      *         If `to` is not an adjacent node `None` is returned.
      */
    def findOutgoingTo(to: NodeT): Option[EdgeT]

    /** Incoming edges of this node.
      * @return set of all edges incoming to of this including undirected edges.
      */
    def incoming: Set[EdgeT] with FilterableSet[EdgeT]

    /** All incoming edges connecting `from` with this node.
      * @param from The node with zero, one or more edges
      *             having this node as a direct successor.
      * @return All edges at `from` having this node as a direct successor.
      *         If `from` equals this node all hooks are returned.
      *         If `from` is not an adjacent node an empty set is returned.
      */
    def incomingFrom(from: NodeT): Set[EdgeT] with FilterableSet[EdgeT]

    /** An edge at `from` having this node as a successor.
      * @param from The node being at an edge which has
      *           this node as a successor.
      * @return An edges at `from` having this node as a successor.
      *         If `from` equals this node a hook may be returned.
      *         If `from` is not an adjacent node `None` is returned.
      */
    def findIncomingFrom(from: NodeT): Option[EdgeT]

    /** The degree of this node.
      * @return the number of edges that connect to this node. An edge that connects
      *         to this node at more than one ends (loop) is counted as much times as
      *         it is connected to this node.
      */
    def degree: Int

    /** `true` if this node's degree equals to 0. */
    @inline final def isIsolated = degree == 0

    /** `true` if this node's degree equals to 1. */
    @inline final def isLeaf = degree == 1

    /** The outgoing degree of this node.
      * @return the number of edges that go out from this node including undirected edges.
      *         Loops count once each.
      */
    def outDegree: Int

    /** The outgoing degree of this node after applying some filters to the outgoing edges and successors.
      */
    def outDegree(
        nodeFilter: NodePredicate,
        edgeFilter: EdgePredicate = anyEdge,
        includeHooks: Boolean = false,
        ignoreMultiEdges: Boolean = true
    ): Int

    /** The incoming degree of this node.
      * @return the number of edges that come in to this node including undirected edges.
      *         Loops count once each.
      */
    def inDegree: Int

    /** The incoming degree of this node after applying some filters to the incoming edges and predecessors. */
    def inDegree(
        nodeFilter: NodePredicate,
        edgeFilter: EdgePredicate = anyEdge,
        includeHooks: Boolean = false,
        ignoreMultiEdges: Boolean = true
    ): Int

    def canEqual(that: Any) = true

    override def equals(other: Any) = other match {
      case that: GraphBase[N, E, CC]#BaseInnerNode =>
        (this eq that) || (that canEqual this) && this.outer == that.outer
      case thatR: AnyRef =>
        val thisN = this.outer.asInstanceOf[AnyRef]
        (thisN eq thatR) || thisN == thatR
      case thatV => this.outer == thatV
    }

    override def hashCode: Int    = outer.##
    override def toString: String = outer.toString
  }

  @transient object Node {
    def apply(node: N)    = newNode(node)
    def unapply(n: NodeT) = Some(n)

    @inline final protected[collection] def addDiSuccessors(node: NodeT, edge: EdgeT, add: NodeT => Unit): Unit =
      node.addDiSuccessors(edge, add)
    @inline final protected[collection] def addDiPredecessors(node: NodeT, edge: EdgeT, add: NodeT => Unit): Unit =
      node.addDiPredecessors(edge, add)
    @inline final protected[collection] def addNeighbors(node: NodeT, edge: EdgeT, add: NodeT => Unit): Unit =
      node.addNeighbors(edge, add)

    /** Allows to call methods of N directly on Node instances. */
    @inline implicit final def toOuter(node: NodeT): N = node.outer
  }
  abstract protected class BaseNodeBase extends BaseInnerNode
  protected def newNode(n: N): NodeT

  /** Base trait for graph `Ordering`s. */
  sealed protected trait ElemOrdering {
    protected def noneCompare: Int = throw new IllegalArgumentException("Unexpected use of None.")
    def isDefined: Boolean         = true
  }

  /** The empty ElemOrdering. */
  object NoOrdering extends ElemOrdering with Serializable

  /** Ordering for the path dependent type NodeT. */
  sealed trait NodeOrdering extends Ordering[NodeT] with ElemOrdering
  object NodeOrdering {

    /** Creates a new NodeOrdering with `compare` calling the supplied `cmp`. */
    def apply(cmp: (NodeT, NodeT) => Int) = new NodeOrdering {
      def compare(a: NodeT, b: NodeT): Int = cmp(a, b)
    }
    object None extends NodeOrdering {
      def compare(a: NodeT, b: NodeT): Int = noneCompare
      override def isDefined               = false
    }
  }

  /** Ordering for the path dependent type EdgeT. */
  sealed trait EdgeOrdering extends Ordering[EdgeT] with ElemOrdering

  /** Ordering for the path dependent type EdgeT. */
  object EdgeOrdering extends Serializable {

    def apply(cmp: (EdgeT, EdgeT) => Int): EdgeOrdering = new EdgeOrdering {
      def compare(a: EdgeT, b: EdgeT): Int = cmp(a, b)
    }

    def apply(weight: EdgeT => Double): EdgeOrdering = new EdgeOrdering {
      def compare(x: EdgeT, y: EdgeT): Int = Ordering[Double].compare(weight(x), weight(y))
    }

    object None extends EdgeOrdering {
      def compare(a: EdgeT, b: EdgeT): Int = noneCompare
      override def isDefined               = false
    }
  }

  final protected lazy val anyOrdering = new AnyOrdering[N]
  final lazy val defaultNodeOrdering   = NodeOrdering((a: NodeT, b: NodeT) => anyOrdering.compare(a.outer, b.outer))
  type NodeSetT <: NodeSet
  trait NodeSet extends AnySet[NodeT] with ExtSetMethods[NodeT] {

    /** This method is called by the primary constructor. It must be defined by the trait
      * responsible for the implementation of the graph representation.
      *
      * @param nodes $INNODES
      * @param edges $INEDGES
      */
    protected[collection] def initialize(nodes: AnyIterable[N], edges: AnyIterable[E]): Unit

    /** Iterator over this `NodeSet` mapped to outer nodes. */
    @inline final def outerIterator: Iterator[N] = iterator map (_.outer)

    /** `Iterable` over this `NodeSet` mapped to outer nodes. */
    @inline final def outerIterable: AnyIterable[N] = map(_.outer)

    /** Converts this node set to a set of outer nodes. */
    def toOuter: Set[N] = {
      val b = Set.newBuilder[N]
      this foreach (b += _)
      b.result()
    }

    /** Finds the inner node corresponding to `outerNode`.
      * @return the inner node wrapped by `Some` if found, otherwise None.
      */
    def find(outerNode: N): Option[NodeT]

    /** Finds the inner node corresponding to `outerNode`.
      * @return the inner node if found, otherwise `NoSuchElementException` is thrown.
      */
    def get(outerNode: N): NodeT

    /** Finds the inner node corresponding to `outerNode`.
      * @return the inner node if found, otherwise `null`.
      */
    def lookup(outerNode: N): NodeT

    def adjacencyListsToString: String =
      (for (n <- this)
        yield n.outer.toString + ": " + ((for (a <- n.diSuccessors) yield a.outer) mkString ",")) mkString "\n"

    def draw(random: Random): NodeT

    def diff(that: AnySet[NodeT]): AnySet[NodeT] = this.toSet diff that

    /** Same as `foldLeft` except the second parameter of `opNode`.
      *
      * @param opNode binary operator that is passed the cumulated value and an outer node.
      */
    final def foldLeftOuter[B](z: B)(opNode: (B, N) => B): B = outerIterator.foldLeft(z)(opNode)
  }

  /** The node (vertex) set of this `Graph` commonly referred to as V(G).
    * @return Set of all contained nodes.
    */
  def nodes: NodeSetT

  type EdgeT <: InnerEdgeLike[NodeT] with BaseInnerEdge
  trait BaseInnerEdge extends InnerEdgeLike[NodeT] with InnerEdge with Equals {
    this: EdgeT =>

    @inline final override def weight: Double = outer.weight

    /** The nodes of this edge which only participate in this edge. */
    def privateNodes: Set[NodeT] = ends.filter(_.edges.size == 1).toSet

    /** All connecting edges, that is all edges with ends incident with this edge including possible loops. */
    def adjacents: Set[EdgeT] = {
      val a = new mutable.EqHashMap[EdgeT, Null]
      ends foreach (n => n.edges foreach (e => a put (e, null)))
      a -= this
      new immutable.EqSet(a)
    }

    override def canEqual(that: Any): Boolean =
      that.isInstanceOf[GraphBase[N, E, CC]#BaseInnerEdge] ||
        that.isInstanceOf[Edge[_]]

    override def equals(other: Any): Boolean = other match {
      case that: GraphBase[N, E, CC]#BaseInnerEdge =>
        (this eq that) ||
        (this.outer eq that.outer) ||
        this.outer == that.outer
      case that: Edge[_] =>
        (this.outer eq that) ||
        this.outer == that
      case _ => false
    }

    override def hashCode: Int    = outer.##
    override def toString: String = outer.toString
  }
  @transient object BaseInnerEdge {
    protected[collection] def apply(outer: E): EdgeT = {
      @inline def lookup(n: N) = nodes lookup n

      val freshNodes = MMap.empty[N, NodeT]

      def mkNode(n: N): NodeT =
        Option(lookup(n)) getOrElse
          freshNodes.getOrElse(
            n,
            newNode(n).tap(freshNodes += n -> _)
          )

      outer match {
        case edge: AnyEdge[N] =>
          val AnyEdge(n_1, n_2) = edge
          @inline def inner(n: N): NodeT = {
            val found = lookup(n)
            if (null eq found) newNode(n) else found
          }
          val inner_1 = inner(n_1)
          val inner_2 = if (n_1 == n_2) inner_1 else inner(n_2)
          newEdge(outer, inner_1, inner_2)
        case diHyper: AnyDiHyperEdge[N] => newDiHyperEdge(outer, diHyper.sources map mkNode, diHyper.targets map mkNode)
        case hyper: AnyHyperEdge[N]     => newHyperEdge(outer, hyper.ends map mkNode)
      }
    }

    def defaultWeight(edge: EdgeT): Double                            = edge.weight
    def weightOrdering[T: Numeric](weightF: EdgeT => T): EdgeOrdering = EdgeOrdering((Ordering by weightF).compare _)
    def weightOrdering(weightF: EdgeT => Double): EdgeOrdering        = EdgeOrdering(weightF)

    object WeightOrdering extends EdgeOrdering {
      def compare(e1: EdgeT, e2: EdgeT): Int = e1.weight compare e2.weight
    }
    object ArityOrdering extends EdgeOrdering {
      def compare(e1: EdgeT, e2: EdgeT): Int = e1.arity compare e2.arity
    }

    /** Allows to call methods of E directly on EdgeT instances. */
    implicit final def toOuterEdge(edge: EdgeT): E = edge.outer
  }

  protected def newHyperEdge(outer: E, nodes: Several[NodeT]): EdgeT
  protected def newDiHyperEdge(outer: E, sources: OneOrMore[NodeT], targets: OneOrMore[NodeT]): EdgeT
  protected def newEdge(outer: E, node_1: NodeT, node_2: NodeT): EdgeT

  final lazy val defaultEdgeOrdering = EdgeOrdering { (a: EdgeT, b: EdgeT) =>
    (a.ends zip b.ends)
      .find(z => z._1 != z._2)
      .map(t => anyOrdering.compare(t._1, t._2))
      .getOrElse(Ordering.Int.compare(a.arity, b.arity))
  }

  type EdgeSetT <: EdgeSet
  trait EdgeSet extends AnySet[EdgeT] with ExtSetMethods[EdgeT] with Serializable {

    /** This method is called by the primary constructor. It must be defined by the trait
      * responsible for the implementation of the graph representation.
      *
      * @param edges $INEDGES
      */
    protected[collection] def initialize(edges: AnyIterable[E]): Unit

    def contains(node: NodeT): Boolean

    /** Finds the inner edge corresponding to `outerEdge`.
      *
      * @return the inner node wrapped by `Some` if found, otherwise None.
      */
    def find(outerEdge: E): Option[EdgeT]

    /** The maximum arity of all edges in this edge set. */
    def maxArity: Int = if (this.isEmpty) 0 else max(BaseInnerEdge.ArityOrdering).arity

    /** `Iterator` over this `EdgeSet` mapped to outer edges. */
    @inline final def outerIterator: Iterator[E] = iterator map (_.outer)

    /** `Iterable` over this `EdgeSet` mapped to outer edges. */
    @inline final def outerIterable: AnyIterable[E] = map(_.outer)

    /** Converts this edge set to a set of outer edges. */
    def toOuter: Set[E] = {
      val b = Set.newBuilder[E]
      this foreach (b += _.outer)
      b.result()
    }

    final def draw(random: Random): EdgeT = (nodes draw random).edges draw random

    final def findElem[B](other: B, correspond: (EdgeT, B) => Boolean): EdgeT = {
      def find(edge: E): EdgeT = correspond match {
        case c: ((EdgeT, E) => Boolean) @unchecked => nodes.lookup(edge.node(0)).edges findElem (edge, c)
        case _                                     => throw new IllegalArgumentException
      }
      other match {
        case OuterEdge(e)     => find(e)
        case e: BaseInnerEdge => find(e.asInstanceOf[EdgeT].outer)
        case _                => null.asInstanceOf[EdgeT]
      }
    }

    def diff(that: AnySet[EdgeT]): AnySet[EdgeT] = this.toSet diff that

    /** Same as `foldLeft` except the second parameter of `opEdge`.
      *
      * @param opEdge binary operator that is passed the cumulated value and an outer edge.
      */
    final def foldLeftOuter[B](z: B)(opEdge: (B, E) => B): B = outerIterator.foldLeft(z)(opEdge)
  }

  /** The edge set of this `Graph` commonly referred to as E(G).
    *
    * @return Set of all contained edges.
    */
  def edges: EdgeSetT
  def totalWeight: Double = edges.foldLeft(0d)(_ + _.weight)
}

object GraphBase {
  val defaultSeparator = ", "
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy