Back to passing tests

This commit is contained in:
Pat Garrity 2026-06-05 21:51:33 -05:00
parent 00174ae81d
commit 44a3bb6a22
Signed by: pfm
GPG key ID: 0DC16BCA24B270C4
6 changed files with 59 additions and 65 deletions

View file

@ -93,12 +93,16 @@ sealed trait Adjacency:
override def equals(that: Any): Boolean = override def equals(that: Any): Boolean =
val tuple = (this, that) val tuple = (this, that)
tuple match tuple match
// Optimize when two default implementations are present.
case (me: DefaultAdjacency, other: DefaultAdjacency) => case (me: DefaultAdjacency, other: DefaultAdjacency) =>
me.neighbors.length == other.neighbors.length me.neighbors.length == other.neighbors.length
&& me.neighbors && me.neighbors
.zip(other.neighbors) .zip(other.neighbors)
.map { case (x, y) => x.sameElements(y) } .map { case (x, y) => x.sameElements(y) }
.forall(result => result) .forall(result => result)
// Any two adjcency lists.
case (me: Adjacency, other: Adjacency) =>
me.entries.equals(other.entries)
case _ => false case _ => false
object Adjacency: object Adjacency:
@ -136,6 +140,15 @@ object Adjacency:
*/ */
final val Single: Adjacency = DefaultAdjacency.Single final val Single: Adjacency = DefaultAdjacency.Single
/** Instantiate a single-vertex adjacency list.
*
* @param vertex
* The vertex.
* @return
* The new [[Adjacency]].
*/
def single(vertex: Vertex): Adjacency = MappedAdjacency.single(vertex)
/** Calculate an [[Adjacency]] from some collection of [[Edge]]. /** Calculate an [[Adjacency]] from some collection of [[Edge]].
* *
* @param numberOfVertices * @param numberOfVertices
@ -152,6 +165,13 @@ object Adjacency:
DefaultAdjacency.fromEdges(numberOfVertices, edges) DefaultAdjacency.fromEdges(numberOfVertices, edges)
/** Calculate an [[Adjacency]] from some collection of [[Edge]]. /** Calculate an [[Adjacency]] from some collection of [[Edge]].
*
* This function uses the maximum vertex value as an upper bound and assumes
* that there are vertices with no neighbors within that bound -- it creates
* some [[DefaultAdjacency]].
*
* Used the mapped version if you want to ensure that _only_ the vertices
* referenced are contained in the result.
* *
* @param edges * @param edges
* The collection of [[Edge]] present in this graph. * The collection of [[Edge]] present in this graph.
@ -163,6 +183,18 @@ object Adjacency:
): Adjacency = ): Adjacency =
DefaultAdjacency.fromEdges(edges) DefaultAdjacency.fromEdges(edges)
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
*
* @param edges
* The collection of [[Edge]] present in this graph.
* @return
* The calculated [[Adjacency]].
*/
def fromEdgesMapped(
edges: Iterable[Edge]
): Adjacency =
MappedAdjacency.fromEdges(edges)
end Adjacency end Adjacency
/** An immutable adjacency list representation. /** An immutable adjacency list representation.

View file

@ -4,11 +4,12 @@ import gs.graph.v0.Vertex
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import scala.collection.mutable.Queue import scala.collection.mutable.Queue
/** Represents a _ Connected Component_. /** Represents a _Connected Component_.
*
* TODO: ADD EDGES!
*/ */
final class ConnectedComponent private (val value: Set[Vertex]): final class ConnectedComponent private (
val value: Set[Vertex],
val adjacency: Adjacency
):
/** @inheritDocs /** @inheritDocs
*/ */
@ -25,9 +26,13 @@ final class ConnectedComponent private (val value: Set[Vertex]):
*/ */
override def toString(): String = override def toString(): String =
val sb = StringBuilder() val sb = StringBuilder()
sb.append("[") sb.append("entries:[\n")
sb.append(value.mkString(",")) sb.append(
sb.append("]") adjacency.entries
.map { case (v, ns) => s"$v -> ${ns.mkString(",")}" }
.mkString("\n")
)
sb.append("\n]")
sb.toString() sb.toString()
/** @return /** @return
@ -53,30 +58,6 @@ end ConnectedComponent
object ConnectedComponent: object ConnectedComponent:
/** Instantiate a new Connected Component from the given collection of
* [[Vertex]].
*
* This constructor ensures that each [[Vertex]] only occurs once.
*
* @param value
* The input collection.
* @return
* The new Connected Component.
*/
def apply(value: Vertex*): ConnectedComponent =
if value.isEmpty then
throw new IllegalArgumentException(
"Empty connected components are not allowed."
)
else new ConnectedComponent(value.toSet)
def of(value: Int*): ConnectedComponent =
if value.isEmpty then
throw new IllegalArgumentException(
"Empty connected components are not allowed."
)
else new ConnectedComponent(value.map(Vertex.apply).toSet)
/** Instantiate a new Connected Component that contains a single [[Vertex]]. /** Instantiate a new Connected Component that contains a single [[Vertex]].
* *
* @param value * @param value
@ -85,7 +66,7 @@ object ConnectedComponent:
* The new Connected Component. * The new Connected Component.
*/ */
def single(value: Vertex): ConnectedComponent = def single(value: Vertex): ConnectedComponent =
new ConnectedComponent(Set(value)) new ConnectedComponent(Set(value), Adjacency.single(value))
given CanEqual[ConnectedComponent, ConnectedComponent] = CanEqual.derived given CanEqual[ConnectedComponent, ConnectedComponent] = CanEqual.derived
@ -120,7 +101,8 @@ object ConnectedComponent:
else () else ()
} }
new ConnectedComponent(b.toSet) val vs = b.toSet
new ConnectedComponent(vs, g.adjacency.subset(vs))
private class Tracker(val data: Array[Boolean]): private class Tracker(val data: Array[Boolean]):
def visit(v: Vertex): Unit = data(v.ordinal) = true def visit(v: Vertex): Unit = data(v.ordinal) = true

View file

@ -22,7 +22,7 @@ class AdjacencyTests extends munit.FunSuite:
private val adj = Adjacency.fromEdges(size, edges) private val adj = Adjacency.fromEdges(size, edges)
test("should provide equality") { test("should provide equality") {
val a2 = Adjacency(adj.neighbors) val a2 = MappedAdjacency.validate(adj.entries)
val a3 = Adjacency.Single val a3 = Adjacency.Single
assertEquals(adj, a2) assertEquals(adj, a2)

View file

@ -37,20 +37,3 @@ class UndirectedGraphTests extends FunSuite:
assertEquals(g.neighbors(Vertex(0)), Vector(Vertex(1), Vertex(2))) assertEquals(g.neighbors(Vertex(0)), Vector(Vertex(1), Vertex(2)))
assertEquals(g.neighbors(Vertex(4)), Vector.empty) assertEquals(g.neighbors(Vertex(4)), Vector.empty)
} }
test("should calculate roots for each connected component") {
val N = Size(10)
val g = Graph.undirected(
numberOfVertices = N,
edges = 0 -> 1,
1 -> 2,
2 -> 3,
4 -> 5,
5 -> 6,
4 -> 6,
7 -> 8
)
val expectedRoots = Vertex.of(0, 4, 7, 9)
assertEquals(g.roots, expectedRoots)
}

View file

@ -11,7 +11,6 @@ class DigraphTests extends FunSuite:
val dg = Digraph.Empty val dg = Digraph.Empty
assertEquals(dg.numberOfVertices, Size.Zero) assertEquals(dg.numberOfVertices, Size.Zero)
assertEquals(dg.isSingle, false) assertEquals(dg.isSingle, false)
assertEquals(dg.roots, Vector.empty)
assertEquals(dg.isEmpty, true) assertEquals(dg.isEmpty, true)
assertEquals(dg.neighbors(Vertex.Zero), Vector.empty) assertEquals(dg.neighbors(Vertex.Zero), Vector.empty)
assertEquals(dg.hasCycle, false) assertEquals(dg.hasCycle, false)
@ -21,7 +20,6 @@ class DigraphTests extends FunSuite:
val dg = Digraph.Single val dg = Digraph.Single
assertEquals(dg.numberOfVertices, Size.One) assertEquals(dg.numberOfVertices, Size.One)
assertEquals(dg.isSingle, true) assertEquals(dg.isSingle, true)
assertEquals(dg.roots, Vertex.of(0))
assertEquals(dg.isEmpty, false) assertEquals(dg.isEmpty, false)
assertEquals(dg.neighbors(Vertex.Zero), Vector.empty) assertEquals(dg.neighbors(Vertex.Zero), Vector.empty)
assertEquals(dg.hasCycle, false) assertEquals(dg.hasCycle, false)

View file

@ -5,6 +5,7 @@ import gs.graph.v0.Edge
import gs.graph.v0.Graph import gs.graph.v0.Graph
import gs.graph.v0.Size import gs.graph.v0.Size
import gs.graph.v0.Vertex import gs.graph.v0.Vertex
import gs.graph.v0.syntax.V
import munit.* import munit.*
class TarjansTests extends FunSuite: class TarjansTests extends FunSuite:
@ -13,13 +14,13 @@ class TarjansTests extends FunSuite:
val v0 = Vertex.Zero val v0 = Vertex.Zero
val v1 = Vertex(1) val v1 = Vertex(1)
val v2 = Vertex(2) val v2 = Vertex(2)
val s1 = StronglyConnectedComponent(v0) val s1 = StronglyConnectedComponent.single(v0)
val s2 = StronglyConnectedComponent(v0, v1, v2) val s2 = StronglyConnectedComponent.of(0 -> 1, 1 -> 2, 2 -> 0)
val s3 = StronglyConnectedComponent(v0) val s3 = StronglyConnectedComponent.single(v0)
assertEquals(s1, s3) assertEquals(s1, s3)
assertNotEquals(s1, s2) assertNotEquals(s1, s2)
assertEquals(s2.value, Set(v0, v1, v2)) assertEquals(s2.vertices, Set(v0, v1, v2))
assertEquals(s2.vertices, Set(v0, v1, v2)) assertEquals(s2.vertices, Set(v0, v1, v2))
assertEquals(s2.size, 3) assertEquals(s2.size, 3)
assertEquals(s2.isSingle, false) assertEquals(s2.isSingle, false)
@ -60,13 +61,12 @@ class TarjansTests extends FunSuite:
) )
val sccs = StronglyConnectedComponent.findAll(g) val sccs = StronglyConnectedComponent.findAll(g)
assertEquals(g.roots, Vector(Vertex.Zero))
assertEquals( assertEquals(
sccs.toSet, sccs.toSet,
Set( Set(
StronglyConnectedComponent.of(0), StronglyConnectedComponent.single(V(0)),
StronglyConnectedComponent.of(1), StronglyConnectedComponent.single(V(1)),
StronglyConnectedComponent.of(2) StronglyConnectedComponent.single(V(2))
) )
) )
} }
@ -99,13 +99,12 @@ class TarjansTests extends FunSuite:
) )
val sccs = StronglyConnectedComponent.findAll(g) val sccs = StronglyConnectedComponent.findAll(g)
assertEquals(g.roots, Vector.empty)
assertEquals( assertEquals(
sccs.toSet, sccs.toSet,
Set( Set(
StronglyConnectedComponent.of(0, 1, 2), StronglyConnectedComponent.of(0 -> 1, 1 -> 2, 2 -> 0),
StronglyConnectedComponent.of(3, 4, 5), StronglyConnectedComponent.of(3 -> 4, 4 -> 5, 5 -> 3),
StronglyConnectedComponent.of(6, 7, 8) StronglyConnectedComponent.of(6 -> 7, 7 -> 8, 8 -> 6)
) )
) )
} }