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)
|
val notContained = vertices.filterNot(contains)
|
||||||
if notContained.nonEmpty then
|
if notContained.nonEmpty then
|
||||||
throw GraphException.AdjacencyDoesNotContainVertices(
|
throw GraphException.AdjacencyDoesNotContainVertices(
|
||||||
vertices,
|
notContained,
|
||||||
numberOfVertices
|
numberOfVertices
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
@ -109,16 +109,6 @@ object Adjacency:
|
||||||
|
|
||||||
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
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
|
/** Create an empty adjacency list for some number of vertices. No vertex has
|
||||||
* any connections to another vertex.
|
* any connections to another vertex.
|
||||||
*
|
*
|
||||||
|
|
@ -269,19 +259,6 @@ final class DefaultAdjacency private (
|
||||||
|
|
||||||
object DefaultAdjacency:
|
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
|
/** Create an empty adjacency list for some number of vertices. No vertex has
|
||||||
* any connections to another vertex.
|
* any connections to another vertex.
|
||||||
*
|
*
|
||||||
|
|
@ -462,7 +439,10 @@ object MappedAdjacency:
|
||||||
): Adjacency =
|
): Adjacency =
|
||||||
val acc = scala.collection.mutable.Map.empty[Vertex, ListBuffer[Vertex]]
|
val acc = scala.collection.mutable.Map.empty[Vertex, ListBuffer[Vertex]]
|
||||||
edges.foreach { edge =>
|
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
|
val neighbors = acc.map { case (v, ns) => v -> ns.toVector }.toMap
|
||||||
new MappedAdjacency(Size.of(neighbors), neighbors)
|
new MappedAdjacency(Size.of(neighbors), neighbors)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
package gs.graph.v0
|
package gs.graph.v0
|
||||||
|
|
||||||
|
/** Root for all exceptions in the `gs-graph` library.
|
||||||
|
*/
|
||||||
sealed abstract class GraphException extends Throwable
|
sealed abstract class GraphException extends Throwable
|
||||||
|
|
||||||
object GraphException:
|
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(
|
case class VertexExceedsGraphBounds(
|
||||||
vertex: Vertex,
|
vertex: Vertex,
|
||||||
bound: Size
|
bound: Size
|
||||||
|
|
@ -12,6 +22,14 @@ object GraphException:
|
||||||
override def getMessage(): String =
|
override def getMessage(): String =
|
||||||
s"Vertex '$vertex' exceeds graph bound of '$bound' vertices."
|
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(
|
case class DataGraphMismatch(
|
||||||
numberOfVertices: Size,
|
numberOfVertices: Size,
|
||||||
quantityOfData: Int
|
quantityOfData: Int
|
||||||
|
|
@ -21,16 +39,14 @@ object GraphException:
|
||||||
s"""Attempted to initialize a data graph with '$numberOfVertices'
|
s"""Attempted to initialize a data graph with '$numberOfVertices'
|
||||||
vertices. '$quantityOfData' data values were provided.""".stripMargin
|
vertices. '$quantityOfData' data values were provided.""".stripMargin
|
||||||
|
|
||||||
case class RootOutOfBounds(
|
/** Thrown when some collection of vertices are expected within an adjacency,
|
||||||
root: Vertex,
|
* but not found.
|
||||||
numberOfVertices: Size
|
*
|
||||||
) extends GraphException:
|
* @param vertices
|
||||||
|
* The collection of expected vertices.
|
||||||
override def getMessage(): String =
|
* @param numberOfVertices
|
||||||
s"""Attempted to instantiate a single-root digraph with root '$root',
|
* The size of the underlying adjacency.
|
||||||
but that root exceeds the graph bound of '$numberOfVertices'
|
*/
|
||||||
vertices.""".stripMargin
|
|
||||||
|
|
||||||
case class AdjacencyDoesNotContainVertices(
|
case class AdjacencyDoesNotContainVertices(
|
||||||
vertices: Iterable[Vertex],
|
vertices: Iterable[Vertex],
|
||||||
numberOfVertices: Size
|
numberOfVertices: Size
|
||||||
|
|
@ -40,6 +56,14 @@ object GraphException:
|
||||||
s"""The following vertices were not found within an Adjacency of size
|
s"""The following vertices were not found within an Adjacency of size
|
||||||
'$numberOfVertices': [${vertices.mkString(", ")}]""".stripMargin
|
'$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(
|
case class NeighborNotContainedByAdjacency(
|
||||||
candidate: Vertex,
|
candidate: Vertex,
|
||||||
vertex: Vertex
|
vertex: Vertex
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ class AdjacencyTests extends munit.FunSuite:
|
||||||
4 -> 6
|
4 -> 6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val vertices =
|
||||||
|
edges.flatMap(edge => List(edge.from, edge.to)).distinct
|
||||||
|
|
||||||
private val adj = Adjacency.fromEdges(size, edges)
|
private val adj = Adjacency.fromEdges(size, edges)
|
||||||
|
|
||||||
test("should provide equality") {
|
test("should provide equality") {
|
||||||
|
|
@ -102,3 +105,75 @@ class AdjacencyTests extends munit.FunSuite:
|
||||||
assertEquals(a.numberOfVertices, expectedSize)
|
assertEquals(a.numberOfVertices, expectedSize)
|
||||||
assertEquals(a.numberOfEdges, Size.of(es))
|
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