From 80284f8013dac356f789676a1c12e85ad027fc8d Mon Sep 17 00:00:00 2001 From: Pat Garrity Date: Thu, 2 Apr 2026 23:04:12 -0500 Subject: [PATCH] Some minor improvements to construction. --- .../main/scala/gs/graph/v0/Adjacency.scala | 37 +++++++++++++-- .../src/main/scala/gs/graph/v0/Edge.scala | 12 +++++ .../src/main/scala/gs/graph/v0/Graph.scala | 38 +++++++++++++++ .../src/main/scala/gs/graph/v0/Vertex.scala | 46 +++++++++++++++++++ .../scala/gs/graph/v0/directed/Digraph.scala | 2 +- .../graph/v0/directed/SingleRootDigraph.scala | 4 +- .../scala/gs/graph/v0/AdjacencyTests.scala | 20 ++++---- .../test/scala/gs/graph/v0/EdgeTests.scala | 2 +- .../scala/gs/graph/v0/directed/DagTests.scala | 37 +++++++-------- 9 files changed, 160 insertions(+), 38 deletions(-) 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 0afa1bb..c592248 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Adjacency.scala @@ -8,7 +8,7 @@ import scala.collection.mutable.ListBuffer * the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some * vertex _to_ another vertex. */ -final class Adjacency(val neighbors: Vector[Vector[Vertex]]): +final class Adjacency private (val neighbors: Vector[Vector[Vertex]]): /** Get the vector of [[Vertex]] that receive a connection _from_ the input * [[Vertex]]. * @@ -118,8 +118,7 @@ object Adjacency: */ final val Single: Adjacency = new Adjacency(Vector(Vector.empty)) - /** Calculate an [[Adjacency]] from some collection of [[Edge]], where those - * edges are assumed to be directed. + /** Calculate an [[Adjacency]] from some collection of [[Edge]]. * * @param numberOfVertices * The number of [[Vertex]] (`N`) in this graph. @@ -128,14 +127,44 @@ object Adjacency: * @return * The calculated [[Adjacency]]. */ - def fromDirectedEdges( + def fromEdges( numberOfVertices: Size, edges: Iterable[Edge] ): Adjacency = + val buffs = Vector.fill(numberOfVertices.value)(ListBuffer.empty[Vertex]) + val _ = edges.foreach { edge => + if edge.v1 >= numberOfVertices || edge.v2 >= numberOfVertices then + throw new IllegalArgumentException( + s"Edge (${edge.v1}, ${edge.v2}) is out of bounds. Maximum vertex value is ${numberOfVertices.value - 1}" + ) + else + val _ = buffs(edge.from.ordinal).addOne(edge.to) + } + new Adjacency(buffs.map(_.distinct.toVector)) + + /** Calculate an [[Adjacency]] from some collection of [[Edge]]. + * + * @param edges + * The collection of [[Edge]] present in this graph. + * @return + * The calculated [[Adjacency]]. + */ + def fromEdges( + edges: Iterable[Edge] + ): Adjacency = + val numberOfVertices = findMaximumVertex(edges) val buffs = Vector.fill(numberOfVertices.value)(ListBuffer.empty[Vertex]) val _ = edges.foreach { edge => val _ = buffs(edge.from.ordinal).addOne(edge.to) } new Adjacency(buffs.map(_.distinct.toVector)) + private def findMaximumVertex(edges: Iterable[Edge]): Vertex = + var maximum = Vertex.Zero + edges.foreach { edge => + if edge.max > maximum then maximum = edge.max + else () + } + maximum + end Adjacency 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 ef91269..bd3ab5b 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Edge.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Edge.scala @@ -16,6 +16,8 @@ final class Edge private ( val v2: Vertex ): + def max: Vertex = if v1 >= v2 then v1 else v2 + /** When considering this edge as _directed_, this function returns the * [[Vertex]] that is the beginning of the connection. * @@ -103,4 +105,14 @@ object Edge: v2: Int ): Edge = Edge(Vertex(v1), Vertex(v2)) + /** Instantiate a list of Edge. + * + * @param edges + * The list of Edge. + * @return + * The captured list of Edge. + */ + def list(edges: (Int, Int)*): List[Edge] = + edges.map(e => apply(e._1, e._2)).toList + end Edge diff --git a/modules/core/src/main/scala/gs/graph/v0/Graph.scala b/modules/core/src/main/scala/gs/graph/v0/Graph.scala index 497f34d..9ea539f 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Graph.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Graph.scala @@ -58,3 +58,41 @@ trait Graph: case _ => false end Graph + +object Graph: + + /** Construct a new [[UndirectedGraph]] from the given [[Edge]]. + * + * @param numberOfVertices + * The number of vertices in this [[Graph]]. + * @param edges + * The [[Edge]] in this graph. + * @return + * The new [[UndirectedGraph]]. + */ + def undirected( + numberOfVertices: Size, + edges: (Int, Int)* + ): UndirectedGraph = + new UndirectedGraph( + numberOfVertices, + Adjacency.fromEdges(numberOfVertices, Edge.list(edges*)) + ) + + /** Construct a new [[UndirectedGraph]] from the given [[Edge]]. + * + * @param edges + * The [[Edge]] in this graph. + * @return + * The new [[UndirectedGraph]]. + */ + def undirected( + edges: (Int, Int)* + ): UndirectedGraph = + val adj = Adjacency.fromEdges(Edge.list(edges*)) + new UndirectedGraph( + adj.numberOfVertices, + adj + ) + +end Graph diff --git a/modules/core/src/main/scala/gs/graph/v0/Vertex.scala b/modules/core/src/main/scala/gs/graph/v0/Vertex.scala index ff65a11..1e35add 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Vertex.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Vertex.scala @@ -8,6 +8,11 @@ package gs.graph.v0 */ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: + /** @return + * The value (ordinal) of the Vertex. + */ + def value: Int = ordinal + /** @inheritDocs */ override def compare(that: Vertex): Int = @@ -37,6 +42,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: */ infix def <(value: Int): Boolean = ordinal < value + /** Is the ordinal of this vertex less than or equal to some integer value? + * + * @param value + * The integer value. + * @return + * True if the ordinal is less than or equal to the integer value. False + * otherwise. + */ + infix def <=(value: Int): Boolean = ordinal <= value + /** Is the ordinal of this vertex greater than some integer value? * * @param value @@ -46,6 +61,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: */ infix def >(value: Int): Boolean = ordinal > value + /** Is the ordinal of this vertex greater than or equal to some integer value? + * + * @param value + * The integer value. + * @return + * True if the ordinal is greater than or equal to the integer value. False + * otherwise. + */ + infix def >=(value: Int): Boolean = ordinal >= value + /** Is the ordinal of this vertex less than some [[Size]] value? * * @param value @@ -55,6 +80,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: */ infix def <(size: Size): Boolean = ordinal < size.value + /** Is the ordinal of this vertex less than or equal to some [[Size]] value? + * + * @param value + * The [[Size]] value. + * @return + * True if the ordinal is less than or equal to the [[Size]] value. False + * otherwise. + */ + infix def <=(size: Size): Boolean = ordinal <= size.value + /** Is the ordinal of this vertex greater than some [[Size]] value? * * @param value @@ -64,6 +99,17 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: */ infix def >(size: Size): Boolean = ordinal > size.value + /** Is the ordinal of this vertex greater than or equal to some [[Size]] + * value? + * + * @param value + * The [[Size]] value. + * @return + * True if the ordinal is greater than or equal to the [[Size]] value. + * False otherwise. + */ + infix def >=(size: Size): Boolean = ordinal >= size.value + object Vertex: /** The fixed value 0 expressed as a [[Vertex]]. 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 7f6b158..4d75708 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 @@ -63,7 +63,7 @@ object Digraph: ): Digraph = new Digraph( numberOfVertices = numberOfVertices, - adjacency = Adjacency.fromDirectedEdges(numberOfVertices, edges), + adjacency = Adjacency.fromEdges(numberOfVertices, edges), roots = findRootsForDirectedEdges(numberOfVertices, edges) ) 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 931ab45..111a3f6 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 @@ -61,7 +61,7 @@ object SingleRootDigraph: if root < numberOfVertices then new SingleRootDigraph( numberOfVertices, - Adjacency.fromDirectedEdges(numberOfVertices, edges), + Adjacency.fromEdges(numberOfVertices, edges), root ) else throw GraphException.RootOutOfBounds(root, numberOfVertices) @@ -86,7 +86,7 @@ object SingleRootDigraph: Some( new SingleRootDigraph( numberOfVertices, - Adjacency.fromDirectedEdges(numberOfVertices, edges), + Adjacency.fromEdges(numberOfVertices, edges), roots(0) ) ) 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 2cf46a2..269efcc 100644 --- a/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala +++ b/modules/core/src/test/scala/gs/graph/v0/AdjacencyTests.scala @@ -5,17 +5,17 @@ class AdjacencyTests extends munit.FunSuite: test("should provide incoming connections") { val N = Size(7) val vs = (0 until N.value).map(Vertex(_)).toArray - val E = List( - Edge(vs(0), vs(1)), - Edge(vs(0), vs(2)), - Edge(vs(0), vs(3)), - Edge(vs(1), vs(4)), - Edge(vs(2), vs(4)), - Edge(vs(3), vs(4)), - Edge(vs(3), vs(5)), - Edge(vs(4), vs(6)) + val E = Edge.list( + 0 -> 1, + 0 -> 2, + 0 -> 3, + 1 -> 4, + 2 -> 4, + 3 -> 4, + 3 -> 5, + 4 -> 6 ) - val A = Adjacency.fromDirectedEdges(N, E) + val A = Adjacency.fromEdges(N, E) assertEquals(A.incoming(vs(0)), Vector.empty) assertEquals(A.incoming(vs(1)), Vector(vs(0))) diff --git a/modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala b/modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala index 94b1e81..c1e2168 100644 --- a/modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala +++ b/modules/core/src/test/scala/gs/graph/v0/EdgeTests.scala @@ -10,7 +10,7 @@ class EdgeTests extends FunSuite: val v3 = Vertex(3) val edge1 = Edge(v1, v2) val edge2 = Edge(v1 -> v2) - val edge3 = new Edge(v3, v1) + val edge3 = Edge(v3, v1) assertEquals(edge1, edge2) assertNotEquals(edge1, edge3) assertEquals(edge1.toString(), s"($v1, $v2)") diff --git a/modules/core/src/test/scala/gs/graph/v0/directed/DagTests.scala b/modules/core/src/test/scala/gs/graph/v0/directed/DagTests.scala index 40cd965..b9b6570 100644 --- a/modules/core/src/test/scala/gs/graph/v0/directed/DagTests.scala +++ b/modules/core/src/test/scala/gs/graph/v0/directed/DagTests.scala @@ -3,7 +3,6 @@ package gs.graph.v0.directed import gs.graph.v0.Adjacency import gs.graph.v0.Edge import gs.graph.v0.Size -import gs.graph.v0.Vertex import munit.* class DagTests extends FunSuite: @@ -23,20 +22,19 @@ class DagTests extends FunSuite: test("should validate a single-root graph") { val size = Size(8) - val vs = (0 until size.value).map(Vertex(_)) val digraph: Digraph = Digraph.fromAdjacency( - Adjacency.fromDirectedEdges( + Adjacency.fromEdges( numberOfVertices = size, - edges = Seq( - Edge(vs(0) -> vs(1)), - Edge(vs(0) -> vs(2)), - Edge(vs(0) -> vs(3)), - Edge(vs(1) -> vs(4)), - Edge(vs(2) -> vs(4)), - Edge(vs(3) -> vs(5)), - Edge(vs(4) -> vs(6)), - Edge(vs(5) -> vs(6)), - Edge(vs(6) -> vs(7)) + edges = Edge.list( + 0 -> 1, + 0 -> 2, + 0 -> 3, + 1 -> 4, + 2 -> 4, + 3 -> 5, + 4 -> 6, + 5 -> 6, + 6 -> 7 ) ) ) @@ -48,15 +46,14 @@ class DagTests extends FunSuite: test("should NOT validate a single-root digraph with a cycle") { val size = Size(4) - val vs = (0 until size.value).map(Vertex(_)) val digraph: Digraph = Digraph.fromAdjacency( - Adjacency.fromDirectedEdges( + Adjacency.fromEdges( numberOfVertices = size, - edges = Seq( - Edge(vs(0) -> vs(1)), - Edge(vs(1) -> vs(2)), - Edge(vs(2) -> vs(3)), - Edge(vs(3) -> vs(1)) + edges = Edge.list( + 0 -> 1, + 1 -> 2, + 2 -> 3, + 3 -> 1 ) ) )