(minor) coverage, fixes, updates
This commit is contained in:
parent
b7feb7a341
commit
95e9ec5e08
18 changed files with 670 additions and 98 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
val scala3: String = "3.8.2"
|
val scala3: String = "3.8.3"
|
||||||
|
|
||||||
ThisBuild / scalaVersion := scala3
|
ThisBuild / scalaVersion := scala3
|
||||||
ThisBuild / versionScheme := Some("semver-spec")
|
ThisBuild / versionScheme := Some("semver-spec")
|
||||||
|
|
@ -38,7 +38,7 @@ val Deps = new {
|
||||||
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1"
|
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.2.4"
|
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val testSettings = Seq(
|
lazy val testSettings = Seq(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import scala.collection.mutable.ListBuffer
|
||||||
* the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some
|
* the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some
|
||||||
* vertex _to_ another vertex.
|
* vertex _to_ another vertex.
|
||||||
*/
|
*/
|
||||||
final class Adjacency(val neighbors: Vector[Vector[Vertex]]):
|
final class Adjacency private (val neighbors: Vector[Vector[Vertex]]):
|
||||||
/** Get the vector of [[Vertex]] that receive a connection _from_ the input
|
/** Get the vector of [[Vertex]] that receive a connection _from_ the input
|
||||||
* [[Vertex]].
|
* [[Vertex]].
|
||||||
*
|
*
|
||||||
|
|
@ -103,7 +103,7 @@ object Adjacency:
|
||||||
* @return
|
* @return
|
||||||
* Some new empty adjacency.
|
* Some new empty adjacency.
|
||||||
*/
|
*/
|
||||||
def empty(numberOfVertices: Size): Adjacency =
|
def noEdges(numberOfVertices: Size): Adjacency =
|
||||||
new Adjacency(Vector.fill(numberOfVertices.value)(Vector.empty))
|
new Adjacency(Vector.fill(numberOfVertices.value)(Vector.empty))
|
||||||
|
|
||||||
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
||||||
|
|
@ -118,8 +118,7 @@ object Adjacency:
|
||||||
*/
|
*/
|
||||||
final val Single: Adjacency = new Adjacency(Vector(Vector.empty))
|
final val Single: Adjacency = new Adjacency(Vector(Vector.empty))
|
||||||
|
|
||||||
/** Calculate an [[Adjacency]] from some collection of [[Edge]], where those
|
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
|
||||||
* edges are assumed to be directed.
|
|
||||||
*
|
*
|
||||||
* @param numberOfVertices
|
* @param numberOfVertices
|
||||||
* The number of [[Vertex]] (`N`) in this graph.
|
* The number of [[Vertex]] (`N`) in this graph.
|
||||||
|
|
@ -128,14 +127,44 @@ object Adjacency:
|
||||||
* @return
|
* @return
|
||||||
* The calculated [[Adjacency]].
|
* The calculated [[Adjacency]].
|
||||||
*/
|
*/
|
||||||
def fromDirectedEdges(
|
def fromEdges(
|
||||||
numberOfVertices: Size,
|
numberOfVertices: Size,
|
||||||
edges: Iterable[Edge]
|
edges: Iterable[Edge]
|
||||||
): Adjacency =
|
): Adjacency =
|
||||||
val buffs = Vector.fill(numberOfVertices.value)(ListBuffer.empty[Vertex])
|
val buffs = Vector.fill(numberOfVertices.value)(ListBuffer.empty[Vertex])
|
||||||
|
val _ = edges.foreach { edge =>
|
||||||
|
if edge.v1 >= numberOfVertices || edge.v2 >= numberOfVertices then
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
s"Edge (${edge.v1}, ${edge.v2}) is out of bounds. Maximum vertex value is ${numberOfVertices.value - 1}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
val _ = buffs(edge.from.ordinal).addOne(edge.to)
|
||||||
|
}
|
||||||
|
new Adjacency(buffs.map(_.distinct.toVector))
|
||||||
|
|
||||||
|
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
|
||||||
|
*
|
||||||
|
* @param edges
|
||||||
|
* The collection of [[Edge]] present in this graph.
|
||||||
|
* @return
|
||||||
|
* The calculated [[Adjacency]].
|
||||||
|
*/
|
||||||
|
def fromEdges(
|
||||||
|
edges: Iterable[Edge]
|
||||||
|
): Adjacency =
|
||||||
|
val numberOfVertices = findMaximumVertex(edges).ordinal + 1
|
||||||
|
val buffs = Vector.fill(numberOfVertices)(ListBuffer.empty[Vertex])
|
||||||
val _ = edges.foreach { edge =>
|
val _ = edges.foreach { edge =>
|
||||||
val _ = buffs(edge.from.ordinal).addOne(edge.to)
|
val _ = buffs(edge.from.ordinal).addOne(edge.to)
|
||||||
}
|
}
|
||||||
new Adjacency(buffs.map(_.distinct.toVector))
|
new Adjacency(buffs.map(_.distinct.toVector))
|
||||||
|
|
||||||
|
private def findMaximumVertex(edges: Iterable[Edge]): Vertex =
|
||||||
|
var maximum = Vertex.Zero
|
||||||
|
edges.foreach { edge =>
|
||||||
|
if edge.max > maximum then maximum = edge.max
|
||||||
|
else ()
|
||||||
|
}
|
||||||
|
maximum
|
||||||
|
|
||||||
end Adjacency
|
end Adjacency
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package gs.graph.v0
|
||||||
|
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
|
|
||||||
/** Represents a relationship between two [[Vertex]].
|
/** Represents a relationship between two distinct [[Vertex]].
|
||||||
*
|
*
|
||||||
* When used is a directed context, the edge goes _from_ `v1` _to_ `v2`.
|
* When used is a directed context, the edge goes _from_ `v1` _to_ `v2`.
|
||||||
*
|
*
|
||||||
|
|
@ -11,11 +11,13 @@ import java.util.Objects
|
||||||
* @param v2
|
* @param v2
|
||||||
* The second [[Vertex]].
|
* The second [[Vertex]].
|
||||||
*/
|
*/
|
||||||
final class Edge(
|
final class Edge private (
|
||||||
val v1: Vertex,
|
val v1: Vertex,
|
||||||
val v2: Vertex
|
val v2: Vertex
|
||||||
):
|
):
|
||||||
|
|
||||||
|
def max: Vertex = if v1 >= v2 then v1 else v2
|
||||||
|
|
||||||
/** When considering this edge as _directed_, this function returns the
|
/** When considering this edge as _directed_, this function returns the
|
||||||
* [[Vertex]] that is the beginning of the connection.
|
* [[Vertex]] that is the beginning of the connection.
|
||||||
*
|
*
|
||||||
|
|
@ -54,11 +56,63 @@ object Edge:
|
||||||
|
|
||||||
given CanEqual[Edge, Edge] = CanEqual.derived
|
given CanEqual[Edge, Edge] = CanEqual.derived
|
||||||
|
|
||||||
|
/** Instantiate a new Edge.
|
||||||
|
*
|
||||||
|
* Throws an exception if the given vertices are the same.
|
||||||
|
*
|
||||||
|
* @param v1
|
||||||
|
* The first [[Vertex]].
|
||||||
|
* @param v2
|
||||||
|
* The second [[Vertex]].
|
||||||
|
* @return
|
||||||
|
* The new edge.
|
||||||
|
*/
|
||||||
def apply(
|
def apply(
|
||||||
v1: Vertex,
|
v1: Vertex,
|
||||||
v2: Vertex
|
v2: Vertex
|
||||||
): Edge = new Edge(v1, v2)
|
): Edge =
|
||||||
|
if v1 != v2 then new Edge(v1, v2)
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Loop edges are not supported. Edges must refer to two distinct vertexes."
|
||||||
|
)
|
||||||
|
|
||||||
def apply(vs: (Vertex, Vertex)): Edge = new Edge(vs._1, vs._2)
|
/** Instantiate a new Edge.
|
||||||
|
*
|
||||||
|
* Throws an exception if the given vertices are the same.
|
||||||
|
*
|
||||||
|
* @param vs
|
||||||
|
* The pair of [[Vertex]].
|
||||||
|
* @return
|
||||||
|
* The new edge.
|
||||||
|
*/
|
||||||
|
def apply(vs: (Vertex, Vertex)): Edge = Edge(vs._1, vs._2)
|
||||||
|
|
||||||
|
/** Instantiate a new Edge.
|
||||||
|
*
|
||||||
|
* Throws an exception if the given vertices are the same or are not valid
|
||||||
|
* [[Vertex]] values.
|
||||||
|
*
|
||||||
|
* @param v1
|
||||||
|
* The first [[Vertex]].
|
||||||
|
* @param v2
|
||||||
|
* The second [[Vertex]].
|
||||||
|
* @return
|
||||||
|
* The new edge.
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
v1: Int,
|
||||||
|
v2: Int
|
||||||
|
): Edge = Edge(Vertex(v1), Vertex(v2))
|
||||||
|
|
||||||
|
/** Instantiate a list of Edge.
|
||||||
|
*
|
||||||
|
* @param edges
|
||||||
|
* The list of Edge.
|
||||||
|
* @return
|
||||||
|
* The captured list of Edge.
|
||||||
|
*/
|
||||||
|
def list(edges: (Int, Int)*): List[Edge] =
|
||||||
|
edges.map(e => apply(e._1, e._2)).toList
|
||||||
|
|
||||||
end Edge
|
end Edge
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,41 @@ trait Graph:
|
||||||
case _ => false
|
case _ => false
|
||||||
|
|
||||||
end Graph
|
end Graph
|
||||||
|
|
||||||
|
object Graph:
|
||||||
|
|
||||||
|
/** Construct a new [[UndirectedGraph]] from the given [[Edge]].
|
||||||
|
*
|
||||||
|
* @param numberOfVertices
|
||||||
|
* The number of vertices in this [[Graph]].
|
||||||
|
* @param edges
|
||||||
|
* The [[Edge]] in this graph.
|
||||||
|
* @return
|
||||||
|
* The new [[UndirectedGraph]].
|
||||||
|
*/
|
||||||
|
def undirected(
|
||||||
|
numberOfVertices: Size,
|
||||||
|
edges: (Int, Int)*
|
||||||
|
): UndirectedGraph =
|
||||||
|
new UndirectedGraph(
|
||||||
|
numberOfVertices,
|
||||||
|
Adjacency.fromEdges(numberOfVertices, Edge.list(edges*))
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Construct a new [[UndirectedGraph]] from the given [[Edge]].
|
||||||
|
*
|
||||||
|
* @param edges
|
||||||
|
* The [[Edge]] in this graph.
|
||||||
|
* @return
|
||||||
|
* The new [[UndirectedGraph]].
|
||||||
|
*/
|
||||||
|
def undirected(
|
||||||
|
edges: (Int, Int)*
|
||||||
|
): UndirectedGraph =
|
||||||
|
val adj = Adjacency.fromEdges(Edge.list(edges*))
|
||||||
|
new UndirectedGraph(
|
||||||
|
adj.numberOfVertices,
|
||||||
|
adj
|
||||||
|
)
|
||||||
|
|
||||||
|
end Graph
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package gs.graph.v0.directed
|
package gs.graph.v0
|
||||||
|
|
||||||
import gs.graph.v0.Graph
|
import gs.graph.v0.Graph
|
||||||
import gs.graph.v0.Vertex
|
import gs.graph.v0.Vertex
|
||||||
|
|
@ -6,8 +6,51 @@ import scala.collection.mutable.ListBuffer
|
||||||
import scala.collection.mutable.Stack
|
import scala.collection.mutable.Stack
|
||||||
|
|
||||||
/** Represents a _Strongly Connected Component_ (SCC).
|
/** Represents a _Strongly Connected Component_ (SCC).
|
||||||
|
*
|
||||||
|
* Each SCC is guaranteed to be non-empty.
|
||||||
*/
|
*/
|
||||||
opaque type SCC = Vector[Vertex]
|
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
|
||||||
|
|
||||||
object SCC:
|
object SCC:
|
||||||
|
|
||||||
|
|
@ -20,36 +63,54 @@ object SCC:
|
||||||
* @return
|
* @return
|
||||||
* The new SCC.
|
* The new SCC.
|
||||||
*/
|
*/
|
||||||
def apply(value: Seq[Vertex]): SCC = value.distinct.toVector
|
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))
|
||||||
|
|
||||||
given CanEqual[SCC, SCC] = CanEqual.derived
|
given CanEqual[SCC, SCC] = CanEqual.derived
|
||||||
|
|
||||||
extension (scc: SCC)
|
/** Alias for `findAll`.
|
||||||
/** @return
|
*
|
||||||
* The underlying value of this SCC.
|
* Implementation of Tarjan's Algorithm for finding all strongly connected
|
||||||
*/
|
* components for some graph.
|
||||||
def unwrap(): Vector[Vertex] = scc
|
*
|
||||||
|
* This algorithm captures directed cycles. Any vertex not part of some
|
||||||
/** @return
|
* directed cycle (e.g. every node in a DAG) forms an SCC by itself.
|
||||||
* 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
|
|
||||||
|
|
||||||
/** Implementation of Tarjan's Algorithm for finding all strongly connected
|
|
||||||
* components for some directed graph.
|
|
||||||
*
|
*
|
||||||
* @param g
|
* @param g
|
||||||
* The directed graph to analyze.
|
* The graph to analyze.
|
||||||
|
* @return
|
||||||
|
* The complete list of [[SCC]] for the input graph.
|
||||||
|
*/
|
||||||
|
def tarjan(g: Graph): List[SCC] = findAll(g)
|
||||||
|
|
||||||
|
/** 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
|
* @return
|
||||||
* The complete list of [[SCC]] for the input graph.
|
* The complete list of [[SCC]] for the input graph.
|
||||||
*/
|
*/
|
||||||
|
|
@ -134,14 +195,22 @@ object SCC:
|
||||||
sccVertex = s.pop()
|
sccVertex = s.pop()
|
||||||
|
|
||||||
scc.addOne(sccVertex)
|
scc.addOne(sccVertex)
|
||||||
output.addOne(SCC(scc.toVector))
|
if !scc.isEmpty then output.addOne(new SCC(scc.toSet))
|
||||||
|
else println("ERROR")
|
||||||
else ()
|
else ()
|
||||||
|
|
||||||
final private class Counter(var i: Int = 0):
|
/** Mutable state wrapper around an integer.
|
||||||
|
*
|
||||||
|
* @param i
|
||||||
|
* The integer value.
|
||||||
|
*/
|
||||||
|
final private class Counter(var i: Int):
|
||||||
|
|
||||||
def increment(): Unit =
|
def increment(): Unit =
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
def get(): Int = i
|
def get(): Int = i
|
||||||
|
|
||||||
|
end Counter
|
||||||
|
|
||||||
end SCC
|
end SCC
|
||||||
|
|
@ -83,17 +83,6 @@ object Size:
|
||||||
if candidate >= 0 then new Size(candidate)
|
if candidate >= 0 then new Size(candidate)
|
||||||
else throw new IllegalArgumentException("Size values must be 0 or greater.")
|
else throw new IllegalArgumentException("Size values must be 0 or greater.")
|
||||||
|
|
||||||
given CanEqual[Size, Size] = CanEqual.derived
|
|
||||||
|
|
||||||
given Ordering[Size] with
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
def compare(
|
|
||||||
x: Size,
|
|
||||||
y: Size
|
|
||||||
): Int = x.value - y.value
|
|
||||||
|
|
||||||
/** Instantiate the size of some array.
|
/** Instantiate the size of some array.
|
||||||
*
|
*
|
||||||
* @param arr
|
* @param arr
|
||||||
|
|
@ -112,4 +101,24 @@ object Size:
|
||||||
*/
|
*/
|
||||||
def fromVector(vec: Vector[?]): Size = new Size(vec.length)
|
def fromVector(vec: Vector[?]): Size = new Size(vec.length)
|
||||||
|
|
||||||
|
/** Instantiate the size of any iterable.
|
||||||
|
*
|
||||||
|
* @param iter
|
||||||
|
* The iterable.
|
||||||
|
* @return
|
||||||
|
* Size representing the total length of the iterable.
|
||||||
|
*/
|
||||||
|
def of(iter: Iterable[?]): Size = new Size(iter.size)
|
||||||
|
|
||||||
|
given CanEqual[Size, Size] = CanEqual.derived
|
||||||
|
|
||||||
|
given Ordering[Size] with
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
def compare(
|
||||||
|
x: Size,
|
||||||
|
y: Size
|
||||||
|
): Int = x.value - y.value
|
||||||
|
|
||||||
end Size
|
end Size
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,24 @@ class UndirectedGraph(
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def selectRoots(): Vector[Vertex] =
|
override def selectRoots(): Vector[Vertex] = roots
|
||||||
if numberOfVertices == Size.Zero then Vector.empty else Vector(Vertex.Zero)
|
|
||||||
|
/** The roots of an undirected graph are identified by arbitrarily selecting
|
||||||
|
* the first listed vertex from each strongly connected component in the
|
||||||
|
* graph.
|
||||||
|
*/
|
||||||
|
lazy val roots: Vector[Vertex] =
|
||||||
|
calculateRoots()
|
||||||
|
|
||||||
|
private def calculateRoots(): Vector[Vertex] =
|
||||||
|
if numberOfVertices == Size.Zero then Vector.empty
|
||||||
|
else oneRootFromEachSCC(calculateSCCs()).toVector
|
||||||
|
|
||||||
|
private def calculateSCCs(): List[SCC] =
|
||||||
|
SCC.findAll(this)
|
||||||
|
|
||||||
|
private def oneRootFromEachSCC(sccs: List[SCC]): List[Vertex] =
|
||||||
|
sccs.map(_.root)
|
||||||
|
|
||||||
end UndirectedGraph
|
end UndirectedGraph
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ package gs.graph.v0
|
||||||
*/
|
*/
|
||||||
final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* The value (ordinal) of the Vertex.
|
||||||
|
*/
|
||||||
|
def value: Int = ordinal
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def compare(that: Vertex): Int =
|
override def compare(that: Vertex): Int =
|
||||||
|
|
@ -37,6 +42,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
||||||
*/
|
*/
|
||||||
infix def <(value: Int): Boolean = ordinal < value
|
infix def <(value: Int): Boolean = ordinal < value
|
||||||
|
|
||||||
|
/** Is the ordinal of this vertex less than or equal to some integer value?
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The integer value.
|
||||||
|
* @return
|
||||||
|
* True if the ordinal is less than or equal to the integer value. False
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
infix def <=(value: Int): Boolean = ordinal <= value
|
||||||
|
|
||||||
/** Is the ordinal of this vertex greater than some integer value?
|
/** Is the ordinal of this vertex greater than some integer value?
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
|
|
@ -46,6 +61,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
||||||
*/
|
*/
|
||||||
infix def >(value: Int): Boolean = ordinal > value
|
infix def >(value: Int): Boolean = ordinal > value
|
||||||
|
|
||||||
|
/** Is the ordinal of this vertex greater than or equal to some integer value?
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The integer value.
|
||||||
|
* @return
|
||||||
|
* True if the ordinal is greater than or equal to the integer value. False
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
infix def >=(value: Int): Boolean = ordinal >= value
|
||||||
|
|
||||||
/** Is the ordinal of this vertex less than some [[Size]] value?
|
/** Is the ordinal of this vertex less than some [[Size]] value?
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
|
|
@ -55,6 +80,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
||||||
*/
|
*/
|
||||||
infix def <(size: Size): Boolean = ordinal < size.value
|
infix def <(size: Size): Boolean = ordinal < size.value
|
||||||
|
|
||||||
|
/** Is the ordinal of this vertex less than or equal to some [[Size]] value?
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The [[Size]] value.
|
||||||
|
* @return
|
||||||
|
* True if the ordinal is less than or equal to the [[Size]] value. False
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
infix def <=(size: Size): Boolean = ordinal <= size.value
|
||||||
|
|
||||||
/** Is the ordinal of this vertex greater than some [[Size]] value?
|
/** Is the ordinal of this vertex greater than some [[Size]] value?
|
||||||
*
|
*
|
||||||
* @param value
|
* @param value
|
||||||
|
|
@ -64,6 +99,17 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
|
||||||
*/
|
*/
|
||||||
infix def >(size: Size): Boolean = ordinal > size.value
|
infix def >(size: Size): Boolean = ordinal > size.value
|
||||||
|
|
||||||
|
/** Is the ordinal of this vertex greater than or equal to some [[Size]]
|
||||||
|
* value?
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The [[Size]] value.
|
||||||
|
* @return
|
||||||
|
* True if the ordinal is greater than or equal to the [[Size]] value.
|
||||||
|
* False otherwise.
|
||||||
|
*/
|
||||||
|
infix def >=(size: Size): Boolean = ordinal >= size.value
|
||||||
|
|
||||||
object Vertex:
|
object Vertex:
|
||||||
|
|
||||||
/** The fixed value 0 expressed as a [[Vertex]].
|
/** The fixed value 0 expressed as a [[Vertex]].
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ object Digraph:
|
||||||
): Digraph =
|
): Digraph =
|
||||||
new Digraph(
|
new Digraph(
|
||||||
numberOfVertices = numberOfVertices,
|
numberOfVertices = numberOfVertices,
|
||||||
adjacency = Adjacency.fromDirectedEdges(numberOfVertices, edges),
|
adjacency = Adjacency.fromEdges(numberOfVertices, edges),
|
||||||
roots = findRootsForDirectedEdges(numberOfVertices, edges)
|
roots = findRootsForDirectedEdges(numberOfVertices, edges)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ object SingleRootDigraph:
|
||||||
if root < numberOfVertices then
|
if root < numberOfVertices then
|
||||||
new SingleRootDigraph(
|
new SingleRootDigraph(
|
||||||
numberOfVertices,
|
numberOfVertices,
|
||||||
Adjacency.fromDirectedEdges(numberOfVertices, edges),
|
Adjacency.fromEdges(numberOfVertices, edges),
|
||||||
root
|
root
|
||||||
)
|
)
|
||||||
else throw GraphException.RootOutOfBounds(root, numberOfVertices)
|
else throw GraphException.RootOutOfBounds(root, numberOfVertices)
|
||||||
|
|
@ -86,7 +86,7 @@ object SingleRootDigraph:
|
||||||
Some(
|
Some(
|
||||||
new SingleRootDigraph(
|
new SingleRootDigraph(
|
||||||
numberOfVertices,
|
numberOfVertices,
|
||||||
Adjacency.fromDirectedEdges(numberOfVertices, edges),
|
Adjacency.fromEdges(numberOfVertices, edges),
|
||||||
roots(0)
|
roots(0)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,91 @@
|
||||||
package gs.graph.v0
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
class AdjacencyTests extends munit.FunSuite:
|
class AdjacencyTests extends munit.FunSuite:
|
||||||
|
|
||||||
test("should provide incoming connections") {
|
private val size = Size(7)
|
||||||
val N = Size(7)
|
private val vs = (0 until size.value).map(Vertex(_)).toArray
|
||||||
val vs = (0 until N.value).map(Vertex(_)).toArray
|
|
||||||
val E = List(
|
|
||||||
Edge(vs(0), vs(1)),
|
|
||||||
Edge(vs(0), vs(2)),
|
|
||||||
Edge(vs(0), vs(3)),
|
|
||||||
Edge(vs(1), vs(4)),
|
|
||||||
Edge(vs(2), vs(4)),
|
|
||||||
Edge(vs(3), vs(4)),
|
|
||||||
Edge(vs(3), vs(5)),
|
|
||||||
Edge(vs(4), vs(6))
|
|
||||||
)
|
|
||||||
val A = Adjacency.fromDirectedEdges(N, E)
|
|
||||||
|
|
||||||
assertEquals(A.incoming(vs(0)), Vector.empty)
|
private val edges = Edge.list(
|
||||||
assertEquals(A.incoming(vs(1)), Vector(vs(0)))
|
0 -> 1,
|
||||||
assertEquals(A.incoming(vs(2)), Vector(vs(0)))
|
0 -> 2,
|
||||||
assertEquals(A.incoming(vs(3)), Vector(vs(0)))
|
0 -> 3,
|
||||||
assertEquals(A.incoming(vs(4)), Vector(vs(1), vs(2), vs(3)))
|
1 -> 4,
|
||||||
assertEquals(A.incoming(vs(5)), Vector(vs(3)))
|
2 -> 4,
|
||||||
assertEquals(A.incoming(vs(6)), Vector(vs(4)))
|
3 -> 4,
|
||||||
|
3 -> 5,
|
||||||
|
4 -> 6
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adj = Adjacency.fromEdges(size, edges)
|
||||||
|
|
||||||
|
test("should provide equality") {
|
||||||
|
val a2 = Adjacency(adj.neighbors)
|
||||||
|
val a3 = Adjacency.Single
|
||||||
|
|
||||||
|
assertEquals(adj, a2)
|
||||||
|
assertNotEquals(adj, a3)
|
||||||
|
assert(!adj.equals(null))
|
||||||
|
assert(!adj.equals("anything"))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should provide incoming connections") {
|
||||||
|
assertEquals(adj.incoming(vs(0)), Vector.empty)
|
||||||
|
assertEquals(adj.incoming(vs(1)), Vector(vs(0)))
|
||||||
|
assertEquals(adj.incoming(vs(2)), Vector(vs(0)))
|
||||||
|
assertEquals(adj.incoming(vs(3)), Vector(vs(0)))
|
||||||
|
assertEquals(adj.incoming(vs(4)), Vector(vs(1), vs(2), vs(3)))
|
||||||
|
assertEquals(adj.incoming(vs(5)), Vector(vs(3)))
|
||||||
|
assertEquals(adj.incoming(vs(6)), Vector(vs(4)))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should show no incoming connections for a vertex not in the graph") {
|
||||||
|
assertEquals(adj.incoming(Vertex(999)), Vector.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should express an adjacency as edges") {
|
||||||
|
val es = adj.toEdges()
|
||||||
|
assertEquals(es, edges.toVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should track the number of vertices in the graph") {
|
||||||
|
assertEquals(adj.numberOfVertices, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should track the number of edges in the graph") {
|
||||||
|
assertEquals(adj.numberOfEdges, Size.of(edges))
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should construct an adjacency with no edges") {
|
||||||
|
val sz = Size(10)
|
||||||
|
val a = Adjacency.noEdges(sz)
|
||||||
|
assertEquals(a.numberOfVertices, sz)
|
||||||
|
assertEquals(a.numberOfEdges, Size.Zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
"should guard against out of bound values when building from edges with an explicit bound"
|
||||||
|
) {
|
||||||
|
val n = Size(2)
|
||||||
|
val es = Edge.list(0 -> 3)
|
||||||
|
Try(Adjacency.fromEdges(n, es)) match
|
||||||
|
case Failure(ex: IllegalArgumentException) =>
|
||||||
|
assertEquals(
|
||||||
|
ex.getMessage(),
|
||||||
|
"Edge (0, 3) is out of bounds. Maximum vertex value is 1"
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
fail(
|
||||||
|
"Expected adjacency construction to fail with an IllegalArgumentException."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should construct from edges, finding the largest known vertex") {
|
||||||
|
val expectedSize = Size(10)
|
||||||
|
val es = Edge.list(0 -> 1, 1 -> 2, 0 -> 9, 3 -> 1)
|
||||||
|
val a = Adjacency.fromEdges(es)
|
||||||
|
assertEquals(a.numberOfVertices, expectedSize)
|
||||||
|
assertEquals(a.numberOfEdges, Size.of(es))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala
Normal file
36
modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import munit.*
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
class EdgeTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should represent an edge with two vertexes") {
|
||||||
|
val v1 = Vertex.Zero
|
||||||
|
val v2 = Vertex(1)
|
||||||
|
val v3 = Vertex(3)
|
||||||
|
val edge1 = Edge(v1, v2)
|
||||||
|
val edge2 = Edge(v1 -> v2)
|
||||||
|
val edge3 = Edge(v3, v1)
|
||||||
|
assertEquals(edge1, edge2)
|
||||||
|
assertNotEquals(edge1, edge3)
|
||||||
|
assertEquals(edge1.toString(), s"($v1, $v2)")
|
||||||
|
assertEquals(edge1.toString(), edge2.toString())
|
||||||
|
assertEquals(edge1.hashCode(), edge2.hashCode())
|
||||||
|
assertNotEquals(edge1.toString(), edge3.toString())
|
||||||
|
assertNotEquals(edge1.hashCode(), edge3.hashCode())
|
||||||
|
assertEquals(edge1.from, v1)
|
||||||
|
assertEquals(edge1.to, v2)
|
||||||
|
assertEquals(edge1.equals("unrelated-type"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should forbid loop edges") {
|
||||||
|
Try(Edge(1, 1)) match
|
||||||
|
case Failure(ex: IllegalArgumentException) =>
|
||||||
|
assertEquals(
|
||||||
|
ex.getMessage(),
|
||||||
|
"Loop edges are not supported. Edges must refer to two distinct vertexes."
|
||||||
|
)
|
||||||
|
case _ => fail("Expected to fail with an IllegalArgumentException")
|
||||||
|
}
|
||||||
53
modules/core/src/test/scala/gs/graph/v0/SizeTests.scala
Normal file
53
modules/core/src/test/scala/gs/graph/v0/SizeTests.scala
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import munit.*
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
class SizeTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should represent a vertex - some integer >= 0") {
|
||||||
|
val s1 = Size.Zero
|
||||||
|
val s2 = Size(0)
|
||||||
|
val s3 = Size(3)
|
||||||
|
assertEquals(s1, s2)
|
||||||
|
assertNotEquals(s1, s3)
|
||||||
|
assertEquals(s1.compare(s2), 0)
|
||||||
|
assertEquals(s1.compare(s3), -3)
|
||||||
|
assertEquals(s1.equals("unrelated-type"), false)
|
||||||
|
assertEquals(s1.hashCode(), s2.hashCode())
|
||||||
|
assertNotEquals(s1.hashCode(), s3.hashCode())
|
||||||
|
assertEquals(s1 < s3, true)
|
||||||
|
assertEquals(s1 < s2, false)
|
||||||
|
assertEquals(s1 > s3, false)
|
||||||
|
assertEquals(s3 > s1, true)
|
||||||
|
assertEquals(s1 < 3, true)
|
||||||
|
assertEquals(s1 < 0, false)
|
||||||
|
assertEquals(s1 > 0, false)
|
||||||
|
assertEquals(s3 > 0, true)
|
||||||
|
assertEquals(s1 < Vertex(3), true)
|
||||||
|
assertEquals(s1 < Vertex.Zero, false)
|
||||||
|
assertEquals(s1 > Vertex.Zero, false)
|
||||||
|
assertEquals(s3 > Vertex.Zero, true)
|
||||||
|
assertEquals(s1.toString(), s2.toString())
|
||||||
|
assertNotEquals(s1.toString(), s3.toString())
|
||||||
|
assertEquals(Size.Zero.value, 0)
|
||||||
|
assertEquals(Size.One.value, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should throw an exception if an invalid value is given") {
|
||||||
|
Try(Size(-1)).toEither.left.toOption match
|
||||||
|
case None => fail("Size instantiation should have failed.")
|
||||||
|
case Some(cause) =>
|
||||||
|
assertEquals(cause.getMessage(), "Size values must be 0 or greater.")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should instantiate from arrays based on the array length") {
|
||||||
|
val n1 = 3
|
||||||
|
val n2 = 5
|
||||||
|
val a1 = Array.fill(n1)(0)
|
||||||
|
val a2 = Array.fill(n2)(0)
|
||||||
|
val s1 = Size.fromArray(a1)
|
||||||
|
val s2 = Size.fromArray(a2)
|
||||||
|
assertEquals(s1.value, n1)
|
||||||
|
assertEquals(s2.value, n2)
|
||||||
|
}
|
||||||
102
modules/core/src/test/scala/gs/graph/v0/TarjansTests.scala
Normal file
102
modules/core/src/test/scala/gs/graph/v0/TarjansTests.scala
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import munit.*
|
||||||
|
|
||||||
|
class UndirectedGraphTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should support an empty graph") {
|
||||||
|
val g = UndirectedGraph.Empty
|
||||||
|
assertEquals(g.numberOfVertices, Size.Zero)
|
||||||
|
assertEquals(g.adjacency, Adjacency.Empty)
|
||||||
|
assertEquals(g.disposition, GraphDisposition.Undirected)
|
||||||
|
assertEquals(g.selectRoots(), Vector.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
"should calculate roots based on an arbitrary vertex from each connected component"
|
||||||
|
) {
|
||||||
|
// TODO: implement - current logic is wrong.
|
||||||
|
}
|
||||||
40
modules/core/src/test/scala/gs/graph/v0/VertexTests.scala
Normal file
40
modules/core/src/test/scala/gs/graph/v0/VertexTests.scala
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import munit.*
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
class VertexTests extends FunSuite:
|
||||||
|
|
||||||
|
test("should represent a vertex - some integer >= 0") {
|
||||||
|
val v1 = Vertex.Zero
|
||||||
|
val v2 = Vertex(0)
|
||||||
|
val v3 = Vertex(3)
|
||||||
|
assertEquals(v1, v2)
|
||||||
|
assertNotEquals(v1, v3)
|
||||||
|
assertEquals(v1.compare(v2), 0)
|
||||||
|
assertEquals(v1.compare(v3), -3)
|
||||||
|
assertEquals(v1.equals("unrelated-type"), false)
|
||||||
|
assertEquals(v1.hashCode(), v2.hashCode())
|
||||||
|
assertNotEquals(v1.hashCode(), v3.hashCode())
|
||||||
|
assertEquals(v1 < v3, true)
|
||||||
|
assertEquals(v1 < v2, false)
|
||||||
|
assertEquals(v1 > v3, false)
|
||||||
|
assertEquals(v3 > v1, true)
|
||||||
|
assertEquals(v1 < 3, true)
|
||||||
|
assertEquals(v1 < 0, false)
|
||||||
|
assertEquals(v1 > 0, false)
|
||||||
|
assertEquals(v3 > 0, true)
|
||||||
|
assertEquals(v1 < Size(3), true)
|
||||||
|
assertEquals(v1 < Size.Zero, false)
|
||||||
|
assertEquals(v1 > Size.Zero, false)
|
||||||
|
assertEquals(v3 > Size.Zero, true)
|
||||||
|
assertEquals(v1.toString(), v2.toString())
|
||||||
|
assertNotEquals(v1.toString(), v3.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should throw an exception if an invalid value is given") {
|
||||||
|
Try(Vertex(-1)).toEither.left.toOption match
|
||||||
|
case None => fail("Vertex instantiation should have failed.")
|
||||||
|
case Some(cause) =>
|
||||||
|
assertEquals(cause.getMessage(), "Vertex values must be 0 or greater.")
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package gs.graph.v0.directed
|
||||||
import gs.graph.v0.Adjacency
|
import gs.graph.v0.Adjacency
|
||||||
import gs.graph.v0.Edge
|
import gs.graph.v0.Edge
|
||||||
import gs.graph.v0.Size
|
import gs.graph.v0.Size
|
||||||
import gs.graph.v0.Vertex
|
|
||||||
import munit.*
|
import munit.*
|
||||||
|
|
||||||
class DagTests extends FunSuite:
|
class DagTests extends FunSuite:
|
||||||
|
|
@ -23,20 +22,19 @@ class DagTests extends FunSuite:
|
||||||
|
|
||||||
test("should validate a single-root graph") {
|
test("should validate a single-root graph") {
|
||||||
val size = Size(8)
|
val size = Size(8)
|
||||||
val vs = (0 until size.value).map(Vertex(_))
|
|
||||||
val digraph: Digraph = Digraph.fromAdjacency(
|
val digraph: Digraph = Digraph.fromAdjacency(
|
||||||
Adjacency.fromDirectedEdges(
|
Adjacency.fromEdges(
|
||||||
numberOfVertices = size,
|
numberOfVertices = size,
|
||||||
edges = Seq(
|
edges = Edge.list(
|
||||||
Edge(vs(0) -> vs(1)),
|
0 -> 1,
|
||||||
Edge(vs(0) -> vs(2)),
|
0 -> 2,
|
||||||
Edge(vs(0) -> vs(3)),
|
0 -> 3,
|
||||||
Edge(vs(1) -> vs(4)),
|
1 -> 4,
|
||||||
Edge(vs(2) -> vs(4)),
|
2 -> 4,
|
||||||
Edge(vs(3) -> vs(5)),
|
3 -> 5,
|
||||||
Edge(vs(4) -> vs(6)),
|
4 -> 6,
|
||||||
Edge(vs(5) -> vs(6)),
|
5 -> 6,
|
||||||
Edge(vs(6) -> vs(7))
|
6 -> 7
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -48,15 +46,14 @@ class DagTests extends FunSuite:
|
||||||
|
|
||||||
test("should NOT validate a single-root digraph with a cycle") {
|
test("should NOT validate a single-root digraph with a cycle") {
|
||||||
val size = Size(4)
|
val size = Size(4)
|
||||||
val vs = (0 until size.value).map(Vertex(_))
|
|
||||||
val digraph: Digraph = Digraph.fromAdjacency(
|
val digraph: Digraph = Digraph.fromAdjacency(
|
||||||
Adjacency.fromDirectedEdges(
|
Adjacency.fromEdges(
|
||||||
numberOfVertices = size,
|
numberOfVertices = size,
|
||||||
edges = Seq(
|
edges = Edge.list(
|
||||||
Edge(vs(0) -> vs(1)),
|
0 -> 1,
|
||||||
Edge(vs(1) -> vs(2)),
|
1 -> 2,
|
||||||
Edge(vs(2) -> vs(3)),
|
2 -> 3,
|
||||||
Edge(vs(3) -> vs(1))
|
3 -> 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
sbt.version=1.12.8
|
sbt.version=1.12.11
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue