WIP: Adding streaming traversal.
All checks were successful
/ Build and Release Library (push) Successful in 1m32s

This commit is contained in:
Pat Garrity 2025-12-28 21:39:16 -06:00
parent 86c067f3fa
commit 14e33a24f9
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
6 changed files with 72 additions and 14 deletions

View file

@ -81,6 +81,11 @@ object Adjacency:
*/
final val Empty: Adjacency = new Adjacency(Vector.empty)
/** @return
* The single-vertex [[Adjacency]] list.
*/
final val Single: Adjacency = new Adjacency(Vector(Vector.empty))
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
*
* @param numberOfVertices

View file

@ -27,6 +27,13 @@ object UndirectedGraph:
/** @return
* An empty [[UndirectedGraph]].
*/
def empty(): UndirectedGraph = new UndirectedGraph(Size.Zero, Adjacency.Empty)
final val Empty: UndirectedGraph =
new UndirectedGraph(Size.Zero, Adjacency.Empty)
/** @return
* An [[UndirectedGraph]] with a single vertex.
*/
final val Single: UndirectedGraph =
new UndirectedGraph(Size.One, Adjacency.Single)
end UndirectedGraph

View file

@ -46,13 +46,14 @@ object Digraph:
/** @return
* An empty [[Digraph]].
*/
def empty(): Digraph = new Digraph(Size.Zero, Adjacency.Empty, Vector.empty)
final val Empty: Digraph =
new Digraph(Size.Zero, Adjacency.Empty, Vector.empty)
/** @return
* A [[Digraph]] with a single [[Vertex]] and no edges.
* A [[Digraph]] with one vertex.
*/
def single(): Digraph =
new Digraph(Size.One, Adjacency(Vector(Vector.empty)), Vector(Vertex.Zero))
final val Single: Digraph =
new Digraph(Size.One, Adjacency.Single, Vector(Vertex.Zero))
def fromEdges(
numberOfVertices: Size,

View file

@ -9,14 +9,14 @@ import munit.*
class DagTests extends FunSuite:
test("should validate an empty graph") {
Dag.validate(Digraph.empty()) match
case Right(dag) => assertEquals(dag, Digraph.empty())
Dag.validate(Digraph.Empty) match
case Right(dag) => assertEquals(dag, Digraph.Empty)
case _ => fail("Expected the empty graph to be validated as a DAG.")
}
test("should validate a graph with one node") {
Dag.validate(Digraph.single()) match
case Right(dag) => assertEquals(dag, Digraph.single())
Dag.validate(Digraph.Single) match
case Right(dag) => assertEquals(dag, Digraph.Single)
case _ =>
fail("Expected a graph with one vertex to be validated as a DAG.")
}

View file

@ -14,6 +14,8 @@ object GraphTraversalFs2:
graph: Graph,
visit: Vertex => F[Out]
): Stream[F, Out] =
if graph.isEmpty then Stream.empty
else
val state = new DfsState(graph.numberOfVertices)
graph
.selectRoots()

View file

@ -0,0 +1,43 @@
package gs.graph.v0.fs2
import cats.effect.IO
import cats.effect.unsafe.IORuntime
import gs.graph.v0.UndirectedGraph
import gs.graph.v0.Vertex
import gs.graph.v0.directed.Digraph
import munit.*
class Fs2DfsTests extends FunSuite:
given IORuntime = IORuntime.global
private def iotest(
name: String
)(
body: => IO[Unit]
)(
using
Location
): Unit =
test(name)(body.unsafeRunSync())
iotest("(DFS) should return an empty stream for an empty graph") {
val s = GraphTraversalFs2.dfs(
UndirectedGraph.Empty,
_ => IO.raiseError(IllegalStateException("Should not reach this point."))
)
s.compile.last.map(result => assertEquals(result, None))
}
iotest(
"(DFS) should return a stream of one for a graph with a single vertex"
) {
val s1 = GraphTraversalFs2.dfs(UndirectedGraph.Single, v => IO(v))
val s2 = GraphTraversalFs2.dfs(Digraph.Single, v => IO(v))
for
r1 <- s1.compile.toList
r2 <- s2.compile.toList
yield
assertEquals(r1, List(Vertex.Zero))
assertEquals(r2, List(Vertex.Zero))
}