Some minor improvements to construction.

This commit is contained in:
Pat Garrity 2026-04-02 23:04:12 -05:00
parent eeb8a7400b
commit 80284f8013
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
9 changed files with 160 additions and 38 deletions

View file

@ -8,7 +8,7 @@ import scala.collection.mutable.ListBuffer
* the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some * the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some
* vertex _to_ another vertex. * 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 /** Get the vector of [[Vertex]] that receive a connection _from_ the input
* [[Vertex]]. * [[Vertex]].
* *
@ -118,8 +118,7 @@ object Adjacency:
*/ */
final val Single: Adjacency = new Adjacency(Vector(Vector.empty)) final val Single: Adjacency = new Adjacency(Vector(Vector.empty))
/** Calculate an [[Adjacency]] from some collection of [[Edge]], where those /** Calculate an [[Adjacency]] from some collection of [[Edge]].
* edges are assumed to be directed.
* *
* @param numberOfVertices * @param numberOfVertices
* The number of [[Vertex]] (`N`) in this graph. * The number of [[Vertex]] (`N`) in this graph.
@ -128,14 +127,44 @@ object Adjacency:
* @return * @return
* The calculated [[Adjacency]]. * The calculated [[Adjacency]].
*/ */
def fromDirectedEdges( def fromEdges(
numberOfVertices: Size, numberOfVertices: Size,
edges: Iterable[Edge] edges: Iterable[Edge]
): Adjacency = ): 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 buffs = Vector.fill(numberOfVertices.value)(ListBuffer.empty[Vertex])
val _ = edges.foreach { edge => val _ = edges.foreach { edge =>
val _ = buffs(edge.from.ordinal).addOne(edge.to) val _ = buffs(edge.from.ordinal).addOne(edge.to)
} }
new Adjacency(buffs.map(_.distinct.toVector)) 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 end Adjacency

View file

@ -16,6 +16,8 @@ final class Edge private (
val v2: Vertex val v2: Vertex
): ):
def max: Vertex = if v1 >= v2 then v1 else v2
/** When considering this edge as _directed_, this function returns the /** When considering this edge as _directed_, this function returns the
* [[Vertex]] that is the beginning of the connection. * [[Vertex]] that is the beginning of the connection.
* *
@ -103,4 +105,14 @@ object Edge:
v2: Int v2: Int
): Edge = Edge(Vertex(v1), Vertex(v2)) ): 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 end Edge

View file

@ -58,3 +58,41 @@ trait Graph:
case _ => false case _ => false
end Graph 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

View file

@ -8,6 +8,11 @@ package gs.graph.v0
*/ */
final class Vertex private (val ordinal: Int) extends Ordered[Vertex]: final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
/** @return
* The value (ordinal) of the Vertex.
*/
def value: Int = ordinal
/** @inheritDocs /** @inheritDocs
*/ */
override def compare(that: Vertex): Int = 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 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? /** Is the ordinal of this vertex greater than some integer value?
* *
* @param value * @param value
@ -46,6 +61,16 @@ final class Vertex private (val ordinal: Int) extends Ordered[Vertex]:
*/ */
infix def >(value: Int): Boolean = ordinal > value 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? /** Is the ordinal of this vertex less than some [[Size]] value?
* *
* @param 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 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? /** Is the ordinal of this vertex greater than some [[Size]] value?
* *
* @param 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 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: object Vertex:
/** The fixed value 0 expressed as a [[Vertex]]. /** The fixed value 0 expressed as a [[Vertex]].

View file

@ -63,7 +63,7 @@ object Digraph:
): Digraph = ): Digraph =
new Digraph( new Digraph(
numberOfVertices = numberOfVertices, numberOfVertices = numberOfVertices,
adjacency = Adjacency.fromDirectedEdges(numberOfVertices, edges), adjacency = Adjacency.fromEdges(numberOfVertices, edges),
roots = findRootsForDirectedEdges(numberOfVertices, edges) roots = findRootsForDirectedEdges(numberOfVertices, edges)
) )

View file

@ -61,7 +61,7 @@ object SingleRootDigraph:
if root < numberOfVertices then if root < numberOfVertices then
new SingleRootDigraph( new SingleRootDigraph(
numberOfVertices, numberOfVertices,
Adjacency.fromDirectedEdges(numberOfVertices, edges), Adjacency.fromEdges(numberOfVertices, edges),
root root
) )
else throw GraphException.RootOutOfBounds(root, numberOfVertices) else throw GraphException.RootOutOfBounds(root, numberOfVertices)
@ -86,7 +86,7 @@ object SingleRootDigraph:
Some( Some(
new SingleRootDigraph( new SingleRootDigraph(
numberOfVertices, numberOfVertices,
Adjacency.fromDirectedEdges(numberOfVertices, edges), Adjacency.fromEdges(numberOfVertices, edges),
roots(0) roots(0)
) )
) )

View file

@ -5,17 +5,17 @@ class AdjacencyTests extends munit.FunSuite:
test("should provide incoming connections") { test("should provide incoming connections") {
val N = Size(7) val N = Size(7)
val vs = (0 until N.value).map(Vertex(_)).toArray val vs = (0 until N.value).map(Vertex(_)).toArray
val E = List( val E = Edge.list(
Edge(vs(0), vs(1)), 0 -> 1,
Edge(vs(0), vs(2)), 0 -> 2,
Edge(vs(0), vs(3)), 0 -> 3,
Edge(vs(1), vs(4)), 1 -> 4,
Edge(vs(2), vs(4)), 2 -> 4,
Edge(vs(3), vs(4)), 3 -> 4,
Edge(vs(3), vs(5)), 3 -> 5,
Edge(vs(4), vs(6)) 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(0)), Vector.empty)
assertEquals(A.incoming(vs(1)), Vector(vs(0))) assertEquals(A.incoming(vs(1)), Vector(vs(0)))

View file

@ -10,7 +10,7 @@ class EdgeTests extends FunSuite:
val v3 = Vertex(3) val v3 = Vertex(3)
val edge1 = Edge(v1, v2) val edge1 = Edge(v1, v2)
val edge2 = Edge(v1 -> v2) val edge2 = Edge(v1 -> v2)
val edge3 = new Edge(v3, v1) val edge3 = Edge(v3, v1)
assertEquals(edge1, edge2) assertEquals(edge1, edge2)
assertNotEquals(edge1, edge3) assertNotEquals(edge1, edge3)
assertEquals(edge1.toString(), s"($v1, $v2)") assertEquals(edge1.toString(), s"($v1, $v2)")

View file

@ -3,7 +3,6 @@ package gs.graph.v0.directed
import gs.graph.v0.Adjacency import gs.graph.v0.Adjacency
import gs.graph.v0.Edge import gs.graph.v0.Edge
import gs.graph.v0.Size import gs.graph.v0.Size
import gs.graph.v0.Vertex
import munit.* import munit.*
class DagTests extends FunSuite: class DagTests extends FunSuite:
@ -23,20 +22,19 @@ class DagTests extends FunSuite:
test("should validate a single-root graph") { test("should validate a single-root graph") {
val size = Size(8) val size = Size(8)
val vs = (0 until size.value).map(Vertex(_))
val digraph: Digraph = Digraph.fromAdjacency( val digraph: Digraph = Digraph.fromAdjacency(
Adjacency.fromDirectedEdges( Adjacency.fromEdges(
numberOfVertices = size, numberOfVertices = size,
edges = Seq( edges = Edge.list(
Edge(vs(0) -> vs(1)), 0 -> 1,
Edge(vs(0) -> vs(2)), 0 -> 2,
Edge(vs(0) -> vs(3)), 0 -> 3,
Edge(vs(1) -> vs(4)), 1 -> 4,
Edge(vs(2) -> vs(4)), 2 -> 4,
Edge(vs(3) -> vs(5)), 3 -> 5,
Edge(vs(4) -> vs(6)), 4 -> 6,
Edge(vs(5) -> vs(6)), 5 -> 6,
Edge(vs(6) -> vs(7)) 6 -> 7
) )
) )
) )
@ -48,15 +46,14 @@ class DagTests extends FunSuite:
test("should NOT validate a single-root digraph with a cycle") { test("should NOT validate a single-root digraph with a cycle") {
val size = Size(4) val size = Size(4)
val vs = (0 until size.value).map(Vertex(_))
val digraph: Digraph = Digraph.fromAdjacency( val digraph: Digraph = Digraph.fromAdjacency(
Adjacency.fromDirectedEdges( Adjacency.fromEdges(
numberOfVertices = size, numberOfVertices = size,
edges = Seq( edges = Edge.list(
Edge(vs(0) -> vs(1)), 0 -> 1,
Edge(vs(1) -> vs(2)), 1 -> 2,
Edge(vs(2) -> vs(3)), 2 -> 3,
Edge(vs(3) -> vs(1)) 3 -> 1
) )
) )
) )