WIP have roots on to construction of graphs
This commit is contained in:
parent
b7feb7a341
commit
eeb8a7400b
8 changed files with 224 additions and 21 deletions
|
|
@ -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,7 +11,7 @@ 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
|
||||||
):
|
):
|
||||||
|
|
@ -54,11 +54,53 @@ 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))
|
||||||
|
|
||||||
end Edge
|
end Edge
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -45,11 +45,20 @@ object SCC:
|
||||||
*/
|
*/
|
||||||
def isSingle: Boolean = scc.size == 1
|
def isSingle: Boolean = scc.size == 1
|
||||||
|
|
||||||
/** Implementation of Tarjan's Algorithm for finding all strongly connected
|
/** Alias for `findAll`.
|
||||||
* 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.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -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,15 @@ object Size:
|
||||||
*/
|
*/
|
||||||
def fromVector(vec: Vector[?]): Size = new Size(vec.length)
|
def fromVector(vec: Vector[?]): Size = new Size(vec.length)
|
||||||
|
|
||||||
|
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(_.vertices.apply(0))
|
||||||
|
|
||||||
end UndirectedGraph
|
end UndirectedGraph
|
||||||
|
|
||||||
|
|
|
||||||
24
modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala
Normal file
24
modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package gs.graph.v0
|
||||||
|
|
||||||
|
import munit.*
|
||||||
|
|
||||||
|
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 = new 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)
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
@ -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.")
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue