Compare commits
No commits in common. "84d96f50284fc3b232719356f573e41dd07f8995" and "80284f8013dac356f789676a1c12e85ad027fc8d" have entirely different histories.
84d96f5028
...
80284f8013
5 changed files with 28 additions and 190 deletions
|
|
@ -1,4 +1,4 @@
|
|||
val scala3: String = "3.8.3"
|
||||
val scala3: String = "3.8.2"
|
||||
|
||||
ThisBuild / scalaVersion := scala3
|
||||
ThisBuild / versionScheme := Some("semver-spec")
|
||||
|
|
|
|||
|
|
@ -6,51 +6,8 @@ import scala.collection.mutable.ListBuffer
|
|||
import scala.collection.mutable.Stack
|
||||
|
||||
/** Represents a _Strongly Connected Component_ (SCC).
|
||||
*
|
||||
* Each SCC is guaranteed to be non-empty.
|
||||
*/
|
||||
final class SCC private (val value: Set[Vertex]):
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def equals(obj: Any): Boolean =
|
||||
obj match
|
||||
case other: SCC => value.equals(other.value)
|
||||
case _ => false
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def hashCode(): Int = value.hashCode()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toString(): String =
|
||||
val sb = StringBuilder()
|
||||
sb.append("[")
|
||||
sb.append(value.mkString(","))
|
||||
sb.append("]")
|
||||
sb.toString()
|
||||
|
||||
/** @return
|
||||
* The number of vertices contained by this SCC.
|
||||
*/
|
||||
def size: Int = value.size
|
||||
|
||||
/** @return
|
||||
* True if this SCC contains a single vertex.
|
||||
*/
|
||||
def isSingle: Boolean = value.size == 1
|
||||
|
||||
/** @return
|
||||
* The set of vertices in the SCC.
|
||||
*/
|
||||
def vertices: Set[Vertex] = value
|
||||
|
||||
/** Retrieve an arbitrary SCC member as the root vertex.
|
||||
*/
|
||||
lazy val root: Vertex = value.toList.head
|
||||
|
||||
end SCC
|
||||
opaque type SCC = Vector[Vertex]
|
||||
|
||||
object SCC:
|
||||
|
||||
|
|
@ -63,38 +20,32 @@ object SCC:
|
|||
* @return
|
||||
* The new SCC.
|
||||
*/
|
||||
def apply(value: Vertex*): SCC =
|
||||
if value.isEmpty then
|
||||
throw new IllegalArgumentException(
|
||||
"Empty strongly connected components (SCC) are not allowed."
|
||||
)
|
||||
else new SCC(value.toSet)
|
||||
|
||||
def of(value: Int*): SCC =
|
||||
if value.isEmpty then
|
||||
throw new IllegalArgumentException(
|
||||
"Empty strongly connected components (SCC) are not allowed."
|
||||
)
|
||||
else new SCC(value.map(Vertex.apply).toSet)
|
||||
|
||||
/** Instantiate a new SCC that contains a single [[Vertex]].
|
||||
*
|
||||
* @param value
|
||||
* The input vertex.
|
||||
* @return
|
||||
* The new SCC.
|
||||
*/
|
||||
def single(value: Vertex): SCC = new SCC(Set(value))
|
||||
def apply(value: Seq[Vertex]): SCC = value.distinct.toVector
|
||||
|
||||
given CanEqual[SCC, SCC] = CanEqual.derived
|
||||
|
||||
extension (scc: SCC)
|
||||
/** @return
|
||||
* The underlying value of this SCC.
|
||||
*/
|
||||
def unwrap(): Vector[Vertex] = scc
|
||||
|
||||
/** @return
|
||||
* The vertices contained by this SCC.
|
||||
*/
|
||||
def vertices: Vector[Vertex] = scc
|
||||
|
||||
/** @return
|
||||
* The number of vertices contained by this SCC.
|
||||
*/
|
||||
def size: Int = scc.size
|
||||
|
||||
/** @return
|
||||
* True if this SCC contains a single vertex.
|
||||
*/
|
||||
def isSingle: Boolean = scc.size == 1
|
||||
|
||||
/** Alias for `findAll`.
|
||||
*
|
||||
* Implementation of Tarjan's Algorithm for finding all strongly connected
|
||||
* components for some graph.
|
||||
*
|
||||
* This algorithm captures directed cycles. Any vertex not part of some
|
||||
* directed cycle (e.g. every node in a DAG) forms an SCC by itself.
|
||||
*
|
||||
* @param g
|
||||
* The graph to analyze.
|
||||
|
|
@ -106,9 +57,6 @@ object SCC:
|
|||
/** Implementation of Tarjan's Algorithm for finding all strongly connected
|
||||
* components for some graph.
|
||||
*
|
||||
* This algorithm captures directed cycles. Any vertex not part of some
|
||||
* directed cycle (e.g. every node in a DAG) forms an SCC by itself.
|
||||
*
|
||||
* @param g
|
||||
* The graph to analyze.
|
||||
* @return
|
||||
|
|
@ -195,22 +143,14 @@ object SCC:
|
|||
sccVertex = s.pop()
|
||||
|
||||
scc.addOne(sccVertex)
|
||||
if !scc.isEmpty then output.addOne(new SCC(scc.toSet))
|
||||
else println("ERROR")
|
||||
output.addOne(SCC(scc.toVector))
|
||||
else ()
|
||||
|
||||
/** Mutable state wrapper around an integer.
|
||||
*
|
||||
* @param i
|
||||
* The integer value.
|
||||
*/
|
||||
final private class Counter(var i: Int):
|
||||
final private class Counter(var i: Int = 0):
|
||||
|
||||
def increment(): Unit =
|
||||
i += 1
|
||||
|
||||
def get(): Int = i
|
||||
|
||||
end Counter
|
||||
|
||||
end SCC
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class UndirectedGraph(
|
|||
SCC.findAll(this)
|
||||
|
||||
private def oneRootFromEachSCC(sccs: List[SCC]): List[Vertex] =
|
||||
sccs.map(_.root)
|
||||
sccs.map(_.vertices.apply(0))
|
||||
|
||||
end UndirectedGraph
|
||||
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
package gs.graph.v0
|
||||
|
||||
import gs.graph.v0.directed.Digraph
|
||||
import munit.*
|
||||
|
||||
class TarjansTests extends FunSuite:
|
||||
|
||||
test("should provide basic SCC representation") {
|
||||
val v0 = Vertex.Zero
|
||||
val v1 = Vertex(1)
|
||||
val v2 = Vertex(2)
|
||||
val s1 = SCC(v0)
|
||||
val s2 = SCC(v0, v1, v2)
|
||||
val s3 = SCC(v0)
|
||||
|
||||
assertEquals(s1, s3)
|
||||
assertNotEquals(s1, s2)
|
||||
assertEquals(s2.value, Set(v0, v1, v2))
|
||||
assertEquals(s2.vertices, Set(v0, v1, v2))
|
||||
assertEquals(s2.size, 3)
|
||||
assertEquals(s2.isSingle, false)
|
||||
assertEquals(s1.isSingle, true)
|
||||
}
|
||||
|
||||
test("should produce an empty list of SCCs for an empty graph") {
|
||||
val g = UndirectedGraph.Empty
|
||||
val sccs = SCC.tarjan(g)
|
||||
val alt = SCC.findAll(g)
|
||||
assertEquals(sccs, Nil)
|
||||
assertEquals(alt, Nil)
|
||||
}
|
||||
|
||||
test(
|
||||
"should produce a list of once SCC with one vertex for a graph with one vertex"
|
||||
) {
|
||||
val g = Graph.undirected(Size.One)
|
||||
val sccs = SCC.findAll(g)
|
||||
val expected = SCC.single(Vertex.Zero)
|
||||
assertEquals(sccs, List(expected))
|
||||
}
|
||||
|
||||
/** Each vertex of a DAG is an SCC.
|
||||
*/
|
||||
test("should identify a single SCC for a single 'line' of vertices") {
|
||||
val n = Size(3)
|
||||
|
||||
// 0 -> 1 -> 2
|
||||
val g = Digraph.fromAdjacency(
|
||||
Adjacency.fromEdges(
|
||||
n,
|
||||
List(
|
||||
Edge(0, 1),
|
||||
Edge(1, 2)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val sccs = SCC.findAll(g)
|
||||
assertEquals(g.roots, Vector(Vertex.Zero))
|
||||
assertEquals(sccs.toSet, Set(SCC.of(0), SCC.of(1), SCC.of(2)))
|
||||
}
|
||||
|
||||
test("should handle a graph with multiple SCCs") {
|
||||
val n = Size(9)
|
||||
|
||||
// Establish discrete cycles
|
||||
// 0 -> 1 -> 2 -> 0
|
||||
// 3 -> 4 -> 5 -> 3
|
||||
// 6 -> 7 -> 8 -> 6
|
||||
val g = Digraph.fromAdjacency(
|
||||
Adjacency.fromEdges(
|
||||
n,
|
||||
List(
|
||||
// Cycle 1
|
||||
Edge(0, 1),
|
||||
Edge(1, 2),
|
||||
Edge(2, 0),
|
||||
// Cycle 2
|
||||
Edge(3, 4),
|
||||
Edge(4, 5),
|
||||
Edge(5, 3),
|
||||
// Cycle 3
|
||||
Edge(6, 7),
|
||||
Edge(7, 8),
|
||||
Edge(8, 6)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val sccs = SCC.findAll(g)
|
||||
assertEquals(g.roots, Vector.empty)
|
||||
assertEquals(
|
||||
sccs.toSet,
|
||||
Set(
|
||||
SCC.of(0, 1, 2),
|
||||
SCC.of(3, 4, 5),
|
||||
SCC.of(6, 7, 8)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
end TarjansTests
|
||||
|
|
@ -1 +1 @@
|
|||
sbt.version=1.12.9
|
||||
sbt.version=1.12.8
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue