More tests, more adjacency cleanup.
This commit is contained in:
parent
44a3bb6a22
commit
57ca573717
4 changed files with 171 additions and 35 deletions
|
|
@ -79,7 +79,7 @@ sealed trait Adjacency:
|
|||
val notContained = vertices.filterNot(contains)
|
||||
if notContained.nonEmpty then
|
||||
throw GraphException.AdjacencyDoesNotContainVertices(
|
||||
vertices,
|
||||
notContained,
|
||||
numberOfVertices
|
||||
)
|
||||
else
|
||||
|
|
@ -109,16 +109,6 @@ object Adjacency:
|
|||
|
||||
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
||||
|
||||
/** Instantiate a new [[Adjacency]] list from a raw representation.
|
||||
*
|
||||
* @param adj
|
||||
* The underlying vector of values.
|
||||
* @return
|
||||
* The new [[Adjacency]].
|
||||
*/
|
||||
def apply(adj: Vector[Vector[Vertex]]): Adjacency =
|
||||
DefaultAdjacency.apply(adj)
|
||||
|
||||
/** Create an empty adjacency list for some number of vertices. No vertex has
|
||||
* any connections to another vertex.
|
||||
*
|
||||
|
|
@ -269,19 +259,6 @@ final class DefaultAdjacency private (
|
|||
|
||||
object DefaultAdjacency:
|
||||
|
||||
/** Instantiate a new [[Adjacency]] list from a raw representation.
|
||||
*
|
||||
* @param adj
|
||||
* The underlying vector of values.
|
||||
* @return
|
||||
* The new [[Adjacency]].
|
||||
*/
|
||||
def apply(adj: Vector[Vector[Vertex]]): Adjacency =
|
||||
new DefaultAdjacency(
|
||||
numberOfVertices = Size.fromVector(adj),
|
||||
neighbors = adj
|
||||
)
|
||||
|
||||
/** Create an empty adjacency list for some number of vertices. No vertex has
|
||||
* any connections to another vertex.
|
||||
*
|
||||
|
|
@ -462,7 +439,10 @@ object MappedAdjacency:
|
|||
): Adjacency =
|
||||
val acc = scala.collection.mutable.Map.empty[Vertex, ListBuffer[Vertex]]
|
||||
edges.foreach { edge =>
|
||||
acc.getOrElseUpdate(edge.from, ListBuffer.empty).addOne(edge.to)
|
||||
val _ = acc.getOrElseUpdate(edge.from, ListBuffer.empty).addOne(edge.to)
|
||||
// Accounts for vertices with no outgoing edges.
|
||||
// They should still be present as vertices in the graph.
|
||||
val _ = acc.getOrElseUpdate(edge.to, ListBuffer.empty)
|
||||
}
|
||||
val neighbors = acc.map { case (v, ns) => v -> ns.toVector }.toMap
|
||||
new MappedAdjacency(Size.of(neighbors), neighbors)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
package gs.graph.v0
|
||||
|
||||
/** Root for all exceptions in the `gs-graph` library.
|
||||
*/
|
||||
sealed abstract class GraphException extends Throwable
|
||||
|
||||
object GraphException:
|
||||
|
||||
/** Thrown when attempting to leverage a vertex with a bounded adjacency, and
|
||||
* the vertex exceeds the bounds of that graph (adjacency).
|
||||
*
|
||||
* @param vertex
|
||||
* The vertex in question.
|
||||
* @param bound
|
||||
* The bound (maximum vertex value) of the graph.
|
||||
*/
|
||||
case class VertexExceedsGraphBounds(
|
||||
vertex: Vertex,
|
||||
bound: Size
|
||||
|
|
@ -12,6 +22,14 @@ object GraphException:
|
|||
override def getMessage(): String =
|
||||
s"Vertex '$vertex' exceeds graph bound of '$bound' vertices."
|
||||
|
||||
/** Thrown when instantiating a data graph, but the wrong amount of data is
|
||||
* provided.
|
||||
*
|
||||
* @param numberOfVertices
|
||||
* The number of vertices in the graph.
|
||||
* @param quantityOfData
|
||||
* The number of data entries provided.
|
||||
*/
|
||||
case class DataGraphMismatch(
|
||||
numberOfVertices: Size,
|
||||
quantityOfData: Int
|
||||
|
|
@ -21,16 +39,14 @@ object GraphException:
|
|||
s"""Attempted to initialize a data graph with '$numberOfVertices'
|
||||
vertices. '$quantityOfData' data values were provided.""".stripMargin
|
||||
|
||||
case class RootOutOfBounds(
|
||||
root: Vertex,
|
||||
numberOfVertices: Size
|
||||
) extends GraphException:
|
||||
|
||||
override def getMessage(): String =
|
||||
s"""Attempted to instantiate a single-root digraph with root '$root',
|
||||
but that root exceeds the graph bound of '$numberOfVertices'
|
||||
vertices.""".stripMargin
|
||||
|
||||
/** Thrown when some collection of vertices are expected within an adjacency,
|
||||
* but not found.
|
||||
*
|
||||
* @param vertices
|
||||
* The collection of expected vertices.
|
||||
* @param numberOfVertices
|
||||
* The size of the underlying adjacency.
|
||||
*/
|
||||
case class AdjacencyDoesNotContainVertices(
|
||||
vertices: Iterable[Vertex],
|
||||
numberOfVertices: Size
|
||||
|
|
@ -40,6 +56,14 @@ object GraphException:
|
|||
s"""The following vertices were not found within an Adjacency of size
|
||||
'$numberOfVertices': [${vertices.mkString(", ")}]""".stripMargin
|
||||
|
||||
/** Thrown when some candidate neighbor is defined, but not contained within
|
||||
* the adjacency as a vertex. This indicates a malformed structure.
|
||||
*
|
||||
* @param candidate
|
||||
* The candidate neighbor vertex.
|
||||
* @param vertex
|
||||
* The vertex that uses the neighbor.
|
||||
*/
|
||||
case class NeighborNotContainedByAdjacency(
|
||||
candidate: Vertex,
|
||||
vertex: Vertex
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ class AdjacencyTests extends munit.FunSuite:
|
|||
4 -> 6
|
||||
)
|
||||
|
||||
private val vertices =
|
||||
edges.flatMap(edge => List(edge.from, edge.to)).distinct
|
||||
|
||||
private val adj = Adjacency.fromEdges(size, edges)
|
||||
|
||||
test("should provide equality") {
|
||||
|
|
@ -102,3 +105,75 @@ class AdjacencyTests extends munit.FunSuite:
|
|||
assertEquals(a.numberOfVertices, expectedSize)
|
||||
assertEquals(a.numberOfEdges, Size.of(es))
|
||||
}
|
||||
|
||||
test(
|
||||
"should fail to take a subset of vertices not contained within the source"
|
||||
) {
|
||||
val subset = Set(V(0), V(1), V(5))
|
||||
val es = Edge.list(0 -> 1, 1 -> 2, 2 -> 0)
|
||||
val a = Adjacency.fromEdges(es)
|
||||
|
||||
Try(a.subset(subset)) match
|
||||
case Failure(ex: GraphException.AdjacencyDoesNotContainVertices) =>
|
||||
assertEquals(ex.vertices, Set(V(5)))
|
||||
assertEquals(ex.numberOfVertices, a.numberOfVertices)
|
||||
case _ => fail("Expected the subset operation to fail.")
|
||||
}
|
||||
|
||||
test("should represent a single arbitrary vertex") {
|
||||
val v = V(9)
|
||||
val a = Adjacency.single(v)
|
||||
assertEquals(a.numberOfVertices, Size.One)
|
||||
assertEquals(a.numberOfEdges, Size.Zero)
|
||||
assertEquals(a.contains(v), true)
|
||||
assertEquals(a.contains(V(0)), false)
|
||||
}
|
||||
|
||||
test("should instantiate an adjacency from mapped edges") {
|
||||
val es = Edge.list(7 -> 8, 8 -> 9, 9 -> 5)
|
||||
val vs = es.flatMap(edge => List(edge.v1, edge.v2))
|
||||
val a = Adjacency.fromEdgesMapped(es)
|
||||
assertEquals(a.numberOfVertices, Size(4))
|
||||
assertEquals(a.numberOfEdges, Size.of(es))
|
||||
es.foreach { edge =>
|
||||
assertEquals(a.contains(edge.v1), true)
|
||||
assertEquals(a.contains(edge.v2), true)
|
||||
}
|
||||
a.vertices.foreach(vertex => assertEquals(vs.contains(vertex), true))
|
||||
assertEquals(a.toEdges(), es.toVector)
|
||||
assertEquals(a.incoming(V(7)), Vector.empty)
|
||||
assertEquals(a.incoming(V(8)), Vector(V(7)))
|
||||
assertEquals(a.incoming(V(9)), Vector(V(8)))
|
||||
assertEquals(a.incoming(V(5)), Vector(V(9)))
|
||||
}
|
||||
|
||||
test(
|
||||
"should return an empty vector when finding a vertex that is not present"
|
||||
) {
|
||||
val es = Edge.list(0 -> 1, 1 -> 2, 2 -> 3)
|
||||
val a1 = Adjacency.fromEdges(es)
|
||||
val a2 = Adjacency.fromEdgesMapped(es)
|
||||
assertEquals(a1.at(V(9)), Vector.empty)
|
||||
assertEquals(a2.at(V(9)), Vector.empty)
|
||||
}
|
||||
|
||||
test("should instantiate an adjacency from edges (size-based)") {
|
||||
val a = Adjacency.fromEdges(edges)
|
||||
a.vertices.foreach(v => assertEquals(vertices.contains(v), true))
|
||||
assertEquals(a.numberOfVertices, Size.of(vertices))
|
||||
assertEquals(a.numberOfEdges, Size.of(edges))
|
||||
}
|
||||
|
||||
test(
|
||||
"should fail to validate a mapped adjacency when a neighbor is not contained in the keyset"
|
||||
) {
|
||||
val input = Map(
|
||||
V(0) -> Vector(V(1), V(2)),
|
||||
V(1) -> Vector.empty
|
||||
)
|
||||
Try(MappedAdjacency.validate(input)) match
|
||||
case Failure(ex: GraphException.NeighborNotContainedByAdjacency) =>
|
||||
assertEquals(ex.candidate, V(2))
|
||||
assertEquals(ex.vertex, V(0))
|
||||
case _ => fail("Expected adjacency validation to fail.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
package gs.graph.v0
|
||||
|
||||
import gs.graph.v0.syntax.V
|
||||
import munit.*
|
||||
|
||||
class GraphExceptionTests extends FunSuite:
|
||||
|
||||
test("should VertexExceedsGraphBounds") {
|
||||
val v = V(2)
|
||||
val b = Size(1)
|
||||
val ex = GraphException.VertexExceedsGraphBounds(v, b)
|
||||
assertEquals(ex.vertex, v)
|
||||
assertEquals(ex.bound, b)
|
||||
assertEquals(
|
||||
ex.getMessage(),
|
||||
s"Vertex '$v' exceeds graph bound of '$b' vertices."
|
||||
)
|
||||
}
|
||||
|
||||
test("should DataGraphMismatch") {
|
||||
val n = Size(2)
|
||||
val q = 1
|
||||
val ex = GraphException.DataGraphMismatch(n, q)
|
||||
assertEquals(ex.numberOfVertices, n)
|
||||
assertEquals(ex.quantityOfData, q)
|
||||
assertEquals(
|
||||
ex.getMessage(),
|
||||
s"""Attempted to initialize a data graph with '$n'
|
||||
vertices. '$q' data values were provided.""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
test("should AdjacencyDoesNotContainVertices") {
|
||||
val vs = List(V(8), V(9))
|
||||
val n = Size(4)
|
||||
val ex = GraphException.AdjacencyDoesNotContainVertices(vs, n)
|
||||
assertEquals(ex.vertices, vs)
|
||||
assertEquals(ex.numberOfVertices, n)
|
||||
assertEquals(
|
||||
ex.getMessage(),
|
||||
s"""The following vertices were not found within an Adjacency of size
|
||||
'$n': [${vs.mkString(", ")}]""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
test("should NeighborNotContainedByAdjacency") {
|
||||
val c = V(9)
|
||||
val v = V(0)
|
||||
val ex = GraphException.NeighborNotContainedByAdjacency(c, v)
|
||||
assertEquals(ex.candidate, c)
|
||||
assertEquals(ex.vertex, v)
|
||||
assertEquals(
|
||||
ex.getMessage(),
|
||||
s"""Attempted to reference neighbor '$c' of vertex '$v', but
|
||||
'$c' is not a valid vertex in the adjacency.""".stripMargin
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue