ietze.pharg_2.11.0.1.1.source-code.DirectedGraph.scala Maven / Gradle / Ivy
The newest version!
package pharg
import scala.collection.mutable
import cats.{Functor, Monoid}
import cats.syntax.functor._
object DirectedGraph {
implicit val directedGraphFunctor: Functor[DirectedGraph] = new Functor[DirectedGraph] {
def map[A, B](fa: DirectedGraph[A])(f: A => B) = fa.copy(
vertices = fa.vertices map f,
edges = fa.edges map (_ map f)
)
}
implicit def monoidDirectedGraph[A]: Monoid[DirectedGraph[A]] =
new Monoid[DirectedGraph[A]] {
def combine(x: DirectedGraph[A], y: DirectedGraph[A]): DirectedGraph[A] = {
DirectedGraph(
x.vertices union y.vertices,
x.edges union y.edges
)
}
def empty: DirectedGraph[A] = DirectedGraph[A](Set.empty, Set.empty)
}
}
case class DirectedGraph[V](
vertices: Set[V],
edges: Set[Edge[V]]
) extends DirectedGraphLike[V]
trait DirectedGraphLike[V] {
type E = Edge[V]
import pharg.{Edge => E}
def vertices: Set[V]
def edges: Set[E]
def n = vertices.size
def m = edges.size
// assert(edges.flatMap(e => List(e.in, e.out)) subsetOf vertices, "Edges can only connect existing vertices")
def isolatedVertices = vertices.filter(degree(_) == 0)
def inDegree(v: V) = predecessors(v).size
def outDegree(v: V) = successors(v).size
def degree(v: V) = inDegree(v) + outDegree(v)
def numElements = n + m
// lazy caching datastructure for successors, predecessors, incomingEdges, outgoingEdges
private def MapVVempty = Map.empty[V, Set[V]].withDefault((v: V) => { /*assert(vertices contains v);*/ Set.empty[V] })
private def MapVEempty = Map.empty[V, Set[E]].withDefault((v: V) => { /*assert(vertices contains v);*/ Set.empty[E] })
lazy val successors: Map[V, Set[V]] = edges.foldLeft(MapVVempty) { case (suc, E(in, out)) => suc + (in -> (suc(in) + out)) }
lazy val predecessors: Map[V, Set[V]] = edges.foldLeft(MapVVempty) { case (pre, E(in, out)) => pre + (out -> (pre(out) + in)) }
lazy val incomingEdges: Map[V, Set[E]] = edges.foldLeft(MapVEempty) { case (incoming, edge @ E(_, out)) => incoming + (out -> (incoming(out) + edge)) }
lazy val outgoingEdges: Map[V, Set[E]] = edges.foldLeft(MapVEempty) { case (outgoing, edge @ E(in, _)) => outgoing + (in -> (outgoing(in) + edge)) }
// lazy val neighbours: Map[V, Set[V]] = successors.foldLeft(predecessors) { case (nei, (v, suc)) => nei + (v -> (nei(v) ++ suc)) }
lazy val neighbours: Map[V, Set[V]] = edges.foldLeft(MapVVempty) {
case (nei, E(in, out)) =>
nei + (in -> (nei(in) + out)) + ((out -> (nei(out) + in)))
}
// def successors(v: V) = { assert(vertices contains v); edges.collect { case E(`v`, out) => out } }
// def predecessors(v: V) = { assert(vertices contains v); edges.collect { case E(in, `v`) => in } }
// def outgoingEdges(v: V) = { assert(vertices contains v); edges.filter(_.in == v) }
// def incomingEdges(v: V) = { assert(vertices contains v); edges.filter(_.out == v) }
// def neighbours(v: V): Set[V] = {
// // assert(vertices contains v)
// edges.collect {
// case E(`v`, out) => out
// case E(in, `v`) => in
// }
// }
// open neighbourhood
def neighbours(vp: (V) => Boolean): Set[V] = {
vertices.filter(vp).flatMap(v => neighbours(v).filterNot(vp))
}
def incidentEdges(v: V): Set[E] = {
// assert(vertices contains v)
edges.filter(_ contains v)
}
def incidentEdges(vp: (V) => Boolean): Set[E] = {
edges.filter(e => vp(e.in) || vp(e.out))
}
def inducedEdges(vp: (V) => Boolean): Set[E] = {
edges.filter(e => vp(e.in) && vp(e.out))
}
def inducedSubGraph(vp: (V) => Boolean): DirectedGraph[V] = {
val subGraph = DirectedGraph(
vertices.filter(vp),
inducedEdges(vp)
)
// assert(subGraph subGraphOf this)
subGraph
}
def filter(vp: (V) => Boolean): DirectedGraph[V] = inducedSubGraph(vp)
def topologicalSort: List[V] = {
// assert(!hasCycle)
var sorted: List[V] = Nil
val unmarked = mutable.HashSet.empty[V] ++ vertices
val tempMarked = mutable.HashSet.empty[V]
while (unmarked.nonEmpty) visit(unmarked.head)
def visit(n: V) {
if (unmarked(n)) {
tempMarked += n
for (m <- successors(n)) visit(m)
unmarked -= n
tempMarked -= n
sorted ::= n
}
}
sorted
}
def depthFirstSearch(start: V, continue: V => Iterable[V] = successors) = new Iterator[V] {
// assert(vertices contains start)
val stack = mutable.Stack(start)
val onStack = mutable.Set[V]()
val seen = mutable.Set[V]()
override def hasNext: Boolean = stack.nonEmpty
override def next: V = {
val current = stack.pop
onStack -= current
seen += current
for (candidate <- continue(current)) {
if (!seen(candidate) && !onStack(candidate)) {
stack push candidate
onStack += candidate
}
}
current
}
}
def reachable(a: V, b: V): Boolean = depthFirstSearch(a, neighbours) contains b
def isPlanar: Boolean = m <= (3 * n - 5) // TODO: http://bkocay.cs.umanitoba.ca/G&G/articles/Planarity.pdf
def isEmpty = vertices.isEmpty
def subGraphOf(superGraph: DirectedGraphLike[V]) = {
(this.vertices subsetOf superGraph.vertices) &&
(this.edges subsetOf superGraph.edges)
}
//TODO: rename? undirected case with n*(n-1)/2?
def isComplete = m == n * (n - 1)
def connectedComponents: Seq[Seq[V]] = connectedComponents(neighbours)
def stronglyConnectedComponents: Seq[Seq[V]] = connectedComponents(successors)
def connectedComponents(continue: V => Iterable[V]): Seq[Seq[V]] = {
val toVisit = mutable.ArrayBuffer.empty ++ vertices
val components = mutable.ArrayBuffer.empty[Seq[V]]
while (toVisit.nonEmpty) {
val start = toVisit.head
val component = depthFirstSearch(start, continue).toSeq
components += component
toVisit --= component
}
components
}
//TODO: optimization: isComplete || depthFirst...
def isConnected = isEmpty || depthFirstSearch(vertices.head, neighbours).size == n
def hasCycle: Boolean = {
//TODO: optimization: use linear time algorithm
val next = mutable.HashSet.empty ++ vertices
while (next.nonEmpty) {
if (cycleAt(next.head)) return true
}
def cycleAt(v: V, visited: Set[V] = Set.empty): Boolean = {
if (visited contains v) return true // found cycle
if (!(next contains v)) return false // we already checked from here, there is definitely no cycle
next -= v
successors(v).exists(cycleAt(_, visited + v))
}
false
}
def isIsomorphicTo(that: DirectedGraphLike[V]): Boolean = {
if (this.n != that.n) return false
if (this.m != that.m) return false
if (this.vertices.toList.map(this.degree).sorted != that.vertices.toList.map(that.degree).sorted) return false
if (this.n > 20) println(s"Isomorphism testing on Graph with ${this.n} vertices...")
val thisLabels = this.vertices
val thatLabels = that.vertices
def prune(vA: V, vB: V, perm: Map[V, V]) = {
// val (vA, vB) = (V(candA), V(candB))
this.inDegree(vA) != that.inDegree(vB) ||
this.outDegree(vA) != that.outDegree(vB) ||
perm.toList.exists { case (a, b) => this.edges.contains(E(a, vA)) != that.edges.contains(E(b, vB)) } ||
perm.toList.exists { case (a, b) => this.edges.contains(E(vA, a)) != that.edges.contains(E(vB, b)) }
// !(this.inducedSubGraph((thisLabels -- perm.keySet - candA).map(V(_))) isIsomorphicTo that.inducedSubGraph((thatLabels -- perm.values - candB).map(V(_))))
}
def recurse(perm: Map[V, V]): Boolean = {
if (perm.size == this.n) {
vertices.map(perm) == that.vertices &&
edges.map(_ map perm) == that.edges
} else {
val thisCandidates = thisLabels -- perm.keySet
val thatCandidates = thatLabels -- perm.values
val thisCandidate = thisCandidates.head
thatCandidates.filterNot(prune(thisCandidate, _, perm)).exists { thatCandidate =>
recurse(perm + (thisCandidate -> thatCandidate))
}
}
}
recurse(Map.empty)
}
}