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
|
||||
|
||||
/** 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`.
|
||||
*
|
||||
|
|
@ -11,7 +11,7 @@ import java.util.Objects
|
|||
* @param v2
|
||||
* The second [[Vertex]].
|
||||
*/
|
||||
final class Edge(
|
||||
final class Edge private (
|
||||
val v1: Vertex,
|
||||
val v2: Vertex
|
||||
):
|
||||
|
|
@ -54,11 +54,53 @@ object Edge:
|
|||
|
||||
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(
|
||||
v1: 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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package gs.graph.v0.directed
|
||||
package gs.graph.v0
|
||||
|
||||
import gs.graph.v0.Graph
|
||||
import gs.graph.v0.Vertex
|
||||
|
|
@ -45,11 +45,20 @@ object SCC:
|
|||
*/
|
||||
def isSingle: Boolean = scc.size == 1
|
||||
|
||||
/** Implementation of Tarjan's Algorithm for finding all strongly connected
|
||||
* components for some directed graph.
|
||||
/** Alias for `findAll`.
|
||||
*
|
||||
* @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
|
||||
* The complete list of [[SCC]] for the input graph.
|
||||
*/
|
||||
|
|
@ -83,17 +83,6 @@ object Size:
|
|||
if candidate >= 0 then new Size(candidate)
|
||||
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.
|
||||
*
|
||||
* @param arr
|
||||
|
|
@ -112,4 +101,15 @@ object Size:
|
|||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -17,8 +17,24 @@ class UndirectedGraph(
|
|||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def selectRoots(): Vector[Vertex] =
|
||||
if numberOfVertices == Size.Zero then Vector.empty else Vector(Vertex.Zero)
|
||||
override def selectRoots(): Vector[Vertex] = roots
|
||||
|
||||
/** 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
|
||||
|
||||
|
|
|
|||
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