From fd1fdc512d11c4876c7f58dc4a2fdbb0d158877f Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Fri, 27 Mar 2026 22:16:48 -0500 Subject: [PATCH] (patch) general code improvements and more documentation --- build.sbt | 8 +-- .../main/scala/gs/graph/v0/Adjacency.scala | 25 +++++-- .../src/main/scala/gs/graph/v0/Edge.scala | 7 ++ .../scala/gs/graph/v0/GraphDisposition.scala | 12 +++- .../scala/gs/graph/v0/directed/Digraph.scala | 4 +- .../graph/v0/directed/SingleRootDigraph.scala | 68 ++++++++++++++++++- .../scala/gs/graph/v0/AdjacencyTests.scala | 2 +- project/build.properties | 2 +- 8 files changed, 111 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index e4debbd..271f169 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3: String = "3.8.1" +val scala3: String = "3.8.2" ThisBuild / scalaVersion := scala3 ThisBuild / versionScheme := Some("semver-spec") @@ -31,14 +31,14 @@ val Deps = new { } val Fs2 = new { - val Core: ModuleID = "co.fs2" %% "fs2-core" % "3.12.2" + val Core: ModuleID = "co.fs2" %% "fs2-core" % "3.13.0" } val Gs = new { - val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.0" + val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1" } - val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.2.1" + val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.2.4" } lazy val testSettings = Seq( diff --git a/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala b/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala index afd3666..0afa1bb 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala @@ -51,10 +51,9 @@ final class Adjacency(val neighbors: Vector[Vector[Vertex]]): tos.map(to => Edge(from, to)).distinct } - /** @return - * The number of vertices represented by this adjacency list. + /** The number of vertices represented by this adjacency list. */ - def numberOfVertices: Size = Size.fromVector(neighbors) + lazy val numberOfVertices: Size = Size.fromVector(neighbors) /** Perform a linear traversal for each [[gs.graph.v0.Vertex]] to calculate * the total number of edges in this adjacency list. @@ -62,9 +61,11 @@ final class Adjacency(val neighbors: Vector[Vector[Vertex]]): * @return * The number of edges in this adjacency list. */ - def numberOfEdges: Size = Size(neighbors.map(_.length).reduce(_ + _)) + lazy val numberOfEdges: Size = Size(neighbors.map(_.length).reduce(_ + _)) - def findRoots(): Vector[Vertex] = + /** All vertices that do not have inbound connections. + */ + lazy val roots: Vector[Vertex] = val counts = Array.fill(neighbors.length)(0) neighbors.foreach { ns => // Each vertex listed here is receiving an inbound connection, if we @@ -94,6 +95,17 @@ object Adjacency: */ def apply(adj: Vector[Vector[Vertex]]): Adjacency = new Adjacency(adj) + /** Create an empty adjacency list for some number of vertices. No vertex has + * any connections to another vertex. + * + * @param numberOfVertices + * The number of vertices in this disconnected graph. + * @return + * Some new empty adjacency. + */ + def empty(numberOfVertices: Size): Adjacency = + new Adjacency(Vector.fill(numberOfVertices.value)(Vector.empty)) + given CanEqual[Adjacency, Adjacency] = CanEqual.derived /** @return @@ -106,7 +118,8 @@ object Adjacency: */ final val Single: Adjacency = new Adjacency(Vector(Vector.empty)) - /** Calculate an [[Adjacency]] from some collection of [[Edge]]. + /** Calculate an [[Adjacency]] from some collection of [[Edge]], where those + * edges are assumed to be directed. * * @param numberOfVertices * The number of [[Vertex]] (`N`) in this graph. diff --git a/modules/core/src/main/scala/gs/graph/v0/Edge.scala b/modules/core/src/main/scala/gs/graph/v0/Edge.scala index bf47e76..71d2435 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Edge.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Edge.scala @@ -1,5 +1,7 @@ package gs.graph.v0 +import java.util.Objects + /** Represents a relationship between two [[Vertex]]. * * When used is a directed context, the edge goes _from_ `v1` _to_ `v2`. @@ -41,6 +43,11 @@ final class Edge( case other: Edge => v1 == other.v1 && v2 == other.v2 case _ => false + /** @inheritDocs + */ + override def hashCode(): Int = + Objects.hash(v1, v2) + end Edge object Edge: diff --git a/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala b/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala index d536194..0e9d0eb 100644 --- a/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala +++ b/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala @@ -1,5 +1,10 @@ package gs.graph.v0 +/** Describes the fundamental disposition of some graph. + * + * @param name + * The string value of this disposition. + */ sealed abstract class GraphDisposition(val name: String): override def equals(that: Any): Boolean = @@ -15,7 +20,12 @@ object GraphDisposition: given CanEqual[GraphDisposition, GraphDisposition] = CanEqual.derived - case object Directed extends GraphDisposition("directed") + /** The graph has directed relationships between vertices. + */ + case object Directed extends GraphDisposition("directed") + + /** The graph has relationships between vertices with no logical direction. + */ case object Undirected extends GraphDisposition("undirected") end GraphDisposition diff --git a/modules/core/src/main/scala/gs/graph/v0/directed/Digraph.scala b/modules/core/src/main/scala/gs/graph/v0/directed/Digraph.scala index 17ef0ed..7f6b158 100644 --- a/modules/core/src/main/scala/gs/graph/v0/directed/Digraph.scala +++ b/modules/core/src/main/scala/gs/graph/v0/directed/Digraph.scala @@ -29,6 +29,8 @@ class Digraph( */ override def selectRoots(): Vector[Vertex] = roots + /** @inheritDocs + */ override def equals(that: Any): Boolean = that match case other: Digraph => @@ -78,7 +80,7 @@ object Digraph: new Digraph( numberOfVertices = adjacency.numberOfVertices, adjacency = adjacency, - roots = adjacency.findRoots() + roots = adjacency.roots ) /** Find all roots for the given collection of [[Edge]]. diff --git a/modules/core/src/main/scala/gs/graph/v0/directed/SingleRootDigraph.scala b/modules/core/src/main/scala/gs/graph/v0/directed/SingleRootDigraph.scala index 48661ad..931ab45 100644 --- a/modules/core/src/main/scala/gs/graph/v0/directed/SingleRootDigraph.scala +++ b/modules/core/src/main/scala/gs/graph/v0/directed/SingleRootDigraph.scala @@ -6,14 +6,30 @@ import gs.graph.v0.GraphException import gs.graph.v0.Size import gs.graph.v0.Vertex +/** Specialization of [[Digraph]] that always has a single root vertex. + * + * @param n + * The number of [[Vertex]] present in this graph. + * @param a + * The [[Adjacency]] that describes this graph. + * @param r + * The singular root [[Vertex]]. + */ class SingleRootDigraph( n: Size, a: Adjacency, - r: Vertex -) extends Digraph(n, a, Vector(r)) + root: Vertex +) extends Digraph(n, a, Vector(root)) object SingleRootDigraph: + /** Attempt to show that the given [[Digraph]] has a single root. + * + * @param dg + * The input [[Digraph]]. + * @return + * [[SingleRootDigraph]] or `None` if the number of roots is not 1. + */ def fromDirectedGraph(dg: Digraph): Option[SingleRootDigraph] = if dg.roots.size == 1 then Some( @@ -25,6 +41,18 @@ object SingleRootDigraph: ) else None + /** Given some edges, build a [[SingleRootDigraph]]. Throw an exception if + * this operation fails. + * + * @param numberOfVertices + * The number of [[Vertex]] in the graph. + * @param edges + * The collection of [[Edge]] that describe the graph. + * @param root + * The root [[Vertex]]. + * @return + * The new [[SingleRootDigraph]]. + */ def fromEdgesUnsafe( numberOfVertices: Size, edges: Iterable[Edge], @@ -38,6 +66,17 @@ object SingleRootDigraph: ) else throw GraphException.RootOutOfBounds(root, numberOfVertices) + /** Given some edges, build a [[SingleRootDigraph]] if that collection + * represents a graph with a single root. + * + * @param numberOfVertices + * The number of [[Vertex]] in the graph. + * @param edges + * The collection of [[Edge]] that describe the graph. + * @return + * The new [[SingleRootDigraph]], or `None` if the edges do not describe a + * digraph with a single root. + */ def fromEdges( numberOfVertices: Size, edges: Iterable[Edge] @@ -53,6 +92,19 @@ object SingleRootDigraph: ) else None + /** Given some [[Adjacency]] and a given root [[Vertex]], instantiate a new + * [[SingleRootDigraph]]. + * + * Throws an exception if the given root is not contained within the + * [[Adjacency]]. + * + * @param adjacency + * The [[Adjacency]] which describes the graph. + * @param root + * The root [[Vertex]]. + * @return + * New [[SingleRootDigraph]] + */ def fromAdjacencyUnsafe( adjacency: Adjacency, root: Vertex @@ -65,10 +117,20 @@ object SingleRootDigraph: ) else throw GraphException.RootOutOfBounds(root, adjacency.numberOfVertices) + /** Given some [[Adjacency]] and a given root [[Vertex]], instantiate a new + * [[SingleRootDigraph]] if the [[Vertex]] is within the graph.. + * + * @param adjacency + * The [[Adjacency]] which describes the graph. + * @param root + * The root [[Vertex]]. + * @return + * New [[SingleRootDigraph]], or `None` if the given root is not valid. + */ def fromAdjacency( adjacency: Adjacency ): Option[SingleRootDigraph] = - val roots = adjacency.findRoots() + val roots = adjacency.roots if roots.size == 1 then Some( new SingleRootDigraph( diff --git a/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala b/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala index 57fbfed..2cf46a2 100644 --- a/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala +++ b/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala @@ -2,7 +2,7 @@ package gs.graph.v0 class AdjacencyTests extends munit.FunSuite: - test("should provide incoming") { + test("should provide incoming connections") { val N = Size(7) val vs = (0 until N.value).map(Vertex(_)).toArray val E = List( diff --git a/project/build.properties b/project/build.properties index 30b7fd9..08a6fc0 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.12.0 +sbt.version=1.12.8 -- 2.43.0