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
* 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

View file

@ -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

View file

@ -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

View file

@ -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]].

View file

@ -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)
)

View file

@ -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)
)
)

View file

@ -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)))

View file

@ -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)")

View file

@ -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
)
)
)