WIP on a refactor and massive set of improvements
This commit is contained in:
parent
180990c934
commit
00174ae81d
13 changed files with 476 additions and 356 deletions
|
|
@ -1,14 +1,32 @@
|
|||
package gs.graph.v0
|
||||
|
||||
import scala.collection.IndexedSeqView
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
/** An immutable adjacency list representation.
|
||||
*
|
||||
* The index of the collection is the "from" [[Vertex]], and each element of
|
||||
* the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some
|
||||
* vertex _to_ another vertex.
|
||||
/** An adjacency list representation.
|
||||
*/
|
||||
final class Adjacency private (val neighbors: Vector[Vector[Vertex]]):
|
||||
sealed trait Adjacency:
|
||||
/** @return
|
||||
* The number of vertices in this adjacency list.
|
||||
*/
|
||||
def numberOfVertices: Size
|
||||
|
||||
/** @return
|
||||
* The number of edges in this adjacency list.
|
||||
*/
|
||||
def numberOfEdges: Size
|
||||
|
||||
/** @return
|
||||
* View over the vertices in this adjacency.
|
||||
*/
|
||||
def vertices: IndexedSeqView[Vertex]
|
||||
|
||||
/** @return
|
||||
* The list of entries in this adjacency. Each entry is a vertex paired
|
||||
* with the neighbors of that vertex.
|
||||
*/
|
||||
def entries: Map[Vertex, Vector[Vertex]]
|
||||
|
||||
/** Get the vector of [[Vertex]] that receive a connection _from_ the input
|
||||
* [[Vertex]].
|
||||
*
|
||||
|
|
@ -18,7 +36,16 @@ final class Adjacency private (val neighbors: Vector[Vector[Vertex]]):
|
|||
* The vector of [[Vertex]] that receive a connection _from_ the input
|
||||
* [[Vertex]].
|
||||
*/
|
||||
def at(vertex: Vertex): Vector[Vertex] = neighbors(vertex.ordinal)
|
||||
def at(vertex: Vertex): Vector[Vertex]
|
||||
|
||||
/** Determine if the given [[Vertex]] is represented within this adjacency.
|
||||
*
|
||||
* @param vertex
|
||||
* The [[Vertex]] to investigate.
|
||||
* @return
|
||||
* `True` if the [[Vertex]] is contained, `false` otherwise.
|
||||
*/
|
||||
def contains(vertex: Vertex): Boolean
|
||||
|
||||
/** Get the vector of _incoming_ [[Vertex]] that point _to_ some input
|
||||
* [[Vertex]].
|
||||
|
|
@ -28,57 +55,47 @@ final class Adjacency private (val neighbors: Vector[Vector[Vertex]]):
|
|||
* @return
|
||||
* The list of [[Vertex]] that have an edge _to_ the input [[Vertex]].
|
||||
*/
|
||||
def incoming(vertex: Vertex): Vector[Vertex] =
|
||||
if vertex.ordinal >= neighbors.length then Vector.empty
|
||||
else
|
||||
neighbors.zipWithIndex
|
||||
.filter {
|
||||
// ignore the neighbors of the input vertex
|
||||
case (_, index) => index != vertex.ordinal
|
||||
}
|
||||
.filter(_._1.contains(vertex))
|
||||
.map(_._2)
|
||||
.map(Vertex(_))
|
||||
def incoming(vertex: Vertex): Vector[Vertex]
|
||||
|
||||
/** Express this [[Adjacency]] as a vector of [[Edge]].
|
||||
*
|
||||
* @return
|
||||
* The vector of [[Edge]] derived from this [[Adjacency]].
|
||||
*/
|
||||
def toEdges(): Vector[Edge] =
|
||||
neighbors.zipWithIndex.flatMap { case (tos, index) =>
|
||||
val from = Vertex(index)
|
||||
tos.map(to => Edge(from, to)).distinct
|
||||
}
|
||||
def toEdges(): Vector[Edge]
|
||||
|
||||
/** The number of vertices represented by this adjacency list.
|
||||
*/
|
||||
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.
|
||||
/** Extract a subset of vertices and their neighbors, such that the neighbors
|
||||
* are defined within the subset.
|
||||
*
|
||||
* Throws an exception if any input [[Vertex]] is not a member of this
|
||||
* adjacency.
|
||||
*
|
||||
* @param vertices
|
||||
* The subset of [[Vertex]].
|
||||
* @return
|
||||
* The number of edges in this adjacency list.
|
||||
* The subset adjacency.
|
||||
*/
|
||||
lazy val numberOfEdges: Size = Size(neighbors.map(_.length).reduce(_ + _))
|
||||
def subset(vertices: Set[Vertex]): Adjacency =
|
||||
val notContained = vertices.filterNot(contains)
|
||||
if notContained.nonEmpty then
|
||||
throw GraphException.AdjacencyDoesNotContainVertices(
|
||||
vertices,
|
||||
numberOfVertices
|
||||
)
|
||||
else
|
||||
MappedAdjacency.validate(vertices.map { vertex =>
|
||||
// Only take the neighbors that are represented within the subset.
|
||||
vertex -> at(vertex).filter(vertices.contains)
|
||||
}.toMap)
|
||||
|
||||
/** All vertices that do not have inbound connections.
|
||||
/** @inheritDocs
|
||||
*/
|
||||
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
|
||||
// assume the graph is directed.
|
||||
ns.foreach(v => counts(v.ordinal) += 1)
|
||||
}
|
||||
counts.filter(_ == 0).map(Vertex(_)).toVector
|
||||
|
||||
override def equals(that: Any): Boolean =
|
||||
that match
|
||||
case other: Adjacency =>
|
||||
neighbors.length == other.neighbors.length
|
||||
&& neighbors
|
||||
val tuple = (this, that)
|
||||
tuple match
|
||||
case (me: DefaultAdjacency, other: DefaultAdjacency) =>
|
||||
me.neighbors.length == other.neighbors.length
|
||||
&& me.neighbors
|
||||
.zip(other.neighbors)
|
||||
.map { case (x, y) => x.sameElements(y) }
|
||||
.forall(result => result)
|
||||
|
|
@ -86,6 +103,8 @@ final class Adjacency private (val neighbors: Vector[Vector[Vertex]]):
|
|||
|
||||
object Adjacency:
|
||||
|
||||
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
||||
|
||||
/** Instantiate a new [[Adjacency]] list from a raw representation.
|
||||
*
|
||||
* @param adj
|
||||
|
|
@ -93,7 +112,8 @@ object Adjacency:
|
|||
* @return
|
||||
* The new [[Adjacency]].
|
||||
*/
|
||||
def apply(adj: Vector[Vector[Vertex]]): Adjacency = new Adjacency(adj)
|
||||
def apply(adj: Vector[Vector[Vertex]]): Adjacency =
|
||||
DefaultAdjacency.apply(adj)
|
||||
|
||||
/** Create an empty adjacency list for some number of vertices. No vertex has
|
||||
* any connections to another vertex.
|
||||
|
|
@ -104,19 +124,156 @@ object Adjacency:
|
|||
* Some new empty adjacency.
|
||||
*/
|
||||
def noEdges(numberOfVertices: Size): Adjacency =
|
||||
new Adjacency(Vector.fill(numberOfVertices.value)(Vector.empty))
|
||||
|
||||
given CanEqual[Adjacency, Adjacency] = CanEqual.derived
|
||||
DefaultAdjacency.noEdges(numberOfVertices)
|
||||
|
||||
/** @return
|
||||
* The empty [[Adjacency]] list.
|
||||
*/
|
||||
final val Empty: Adjacency = new Adjacency(Vector.empty)
|
||||
final val Empty: Adjacency = DefaultAdjacency.Empty
|
||||
|
||||
/** @return
|
||||
* The single-vertex [[Adjacency]] list.
|
||||
*/
|
||||
final val Single: Adjacency = new Adjacency(Vector(Vector.empty))
|
||||
final val Single: Adjacency = DefaultAdjacency.Single
|
||||
|
||||
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
|
||||
*
|
||||
* @param numberOfVertices
|
||||
* The number of [[Vertex]] (`N`) in this graph.
|
||||
* @param edges
|
||||
* The collection of [[Edge]] present in this graph.
|
||||
* @return
|
||||
* The calculated [[Adjacency]].
|
||||
*/
|
||||
def fromEdges(
|
||||
numberOfVertices: Size,
|
||||
edges: Iterable[Edge]
|
||||
): Adjacency =
|
||||
DefaultAdjacency.fromEdges(numberOfVertices, edges)
|
||||
|
||||
/** 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 =
|
||||
DefaultAdjacency.fromEdges(edges)
|
||||
|
||||
end Adjacency
|
||||
|
||||
/** An immutable adjacency list representation.
|
||||
*
|
||||
* This implementation uses a 0-based indexing system, where each vertex has an
|
||||
* index from 0 to N-1. See [[MappedAdjacency]] for an implementation that does
|
||||
* not require a complete ordered set.
|
||||
*
|
||||
* The index of the collection is the "from" [[Vertex]], and each element of
|
||||
* the corresponding vector is a "to" [[Vertex]] -- there are edges _from_ some
|
||||
* vertex _to_ another vertex.
|
||||
*
|
||||
* @param numberOfVertices
|
||||
* The number of vertices represented by this [[Adjacency]].
|
||||
* @param neighbors
|
||||
* The complete set of relationships.
|
||||
*/
|
||||
final class DefaultAdjacency private (
|
||||
val numberOfVertices: Size,
|
||||
val neighbors: Vector[Vector[Vertex]]
|
||||
) extends Adjacency:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def vertices: IndexedSeqView[Vertex] =
|
||||
(0 until numberOfVertices.value).view.map(Vertex(_))
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def entries: Map[Vertex, Vector[Vertex]] =
|
||||
neighbors.zipWithIndex.map { case (ns, index) => Vertex(index) -> ns }.toMap
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def at(vertex: Vertex): Vector[Vertex] =
|
||||
if vertex < numberOfVertices then neighbors(vertex.ordinal)
|
||||
else Vector.empty
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def contains(vertex: Vertex): Boolean =
|
||||
vertex < numberOfVertices
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def incoming(vertex: Vertex): Vector[Vertex] =
|
||||
if vertex >= numberOfVertices then Vector.empty
|
||||
else
|
||||
neighbors.zipWithIndex
|
||||
.filter {
|
||||
// ignore the neighbors of the input vertex
|
||||
// select those that reference the input
|
||||
case (values, index) =>
|
||||
(index != vertex.ordinal) && values.contains(vertex)
|
||||
}
|
||||
.map(_._2)
|
||||
.map(Vertex(_))
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toEdges(): Vector[Edge] =
|
||||
neighbors.zipWithIndex.flatMap { case (tos, index) =>
|
||||
val from = Vertex(index)
|
||||
tos.map(to => Edge(from, to)).distinct
|
||||
}
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override lazy val numberOfEdges: Size = Size(
|
||||
neighbors.map(_.length).reduce(_ + _)
|
||||
)
|
||||
|
||||
object DefaultAdjacency:
|
||||
|
||||
/** Instantiate a new [[Adjacency]] list from a raw representation.
|
||||
*
|
||||
* @param adj
|
||||
* The underlying vector of values.
|
||||
* @return
|
||||
* The new [[Adjacency]].
|
||||
*/
|
||||
def apply(adj: Vector[Vector[Vertex]]): Adjacency =
|
||||
new DefaultAdjacency(
|
||||
numberOfVertices = Size.fromVector(adj),
|
||||
neighbors = 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 noEdges(numberOfVertices: Size): Adjacency =
|
||||
new DefaultAdjacency(
|
||||
numberOfVertices = numberOfVertices,
|
||||
neighbors = Vector.fill(numberOfVertices.value)(Vector.empty)
|
||||
)
|
||||
|
||||
/** @return
|
||||
* The empty [[Adjacency]] list.
|
||||
*/
|
||||
final val Empty: Adjacency = new DefaultAdjacency(Size.Zero, Vector.empty)
|
||||
|
||||
/** @return
|
||||
* The single-vertex [[Adjacency]] list.
|
||||
*/
|
||||
final val Single: Adjacency =
|
||||
new DefaultAdjacency(Size.One, Vector(Vector.empty))
|
||||
|
||||
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
|
||||
*
|
||||
|
|
@ -140,7 +297,7 @@ object Adjacency:
|
|||
else
|
||||
val _ = buffs(edge.from.ordinal).addOne(edge.to)
|
||||
}
|
||||
new Adjacency(buffs.map(_.distinct.toVector))
|
||||
new DefaultAdjacency(numberOfVertices, buffs.map(_.distinct.toVector))
|
||||
|
||||
/** Calculate an [[Adjacency]] from some collection of [[Edge]].
|
||||
*
|
||||
|
|
@ -157,7 +314,7 @@ object Adjacency:
|
|||
val _ = edges.foreach { edge =>
|
||||
val _ = buffs(edge.from.ordinal).addOne(edge.to)
|
||||
}
|
||||
new Adjacency(buffs.map(_.distinct.toVector))
|
||||
new DefaultAdjacency(Size(numberOfVertices), buffs.map(_.distinct.toVector))
|
||||
|
||||
private def findMaximumVertex(edges: Iterable[Edge]): Vertex =
|
||||
var maximum = Vertex.Zero
|
||||
|
|
@ -167,4 +324,115 @@ object Adjacency:
|
|||
}
|
||||
maximum
|
||||
|
||||
end Adjacency
|
||||
end DefaultAdjacency
|
||||
|
||||
/** An immutable adjacency list implementation.
|
||||
*
|
||||
* This implementation maps [[Vertex]] values to lists of their neighbors.
|
||||
*
|
||||
* @param numberOfVertices
|
||||
* The number of vertices contained in the adjacency.
|
||||
* @param neighbors
|
||||
* The list of neighbors for each vertex in the adjacency.
|
||||
*/
|
||||
final class MappedAdjacency private (
|
||||
val numberOfVertices: Size,
|
||||
val neighbors: Map[Vertex, Vector[Vertex]]
|
||||
) extends Adjacency:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def vertices: IndexedSeqView[Vertex] =
|
||||
neighbors.keySet.toIndexedSeq.view
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def entries: Map[Vertex, Vector[Vertex]] =
|
||||
neighbors
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override lazy val numberOfEdges: Size =
|
||||
Size(neighbors.map(_._2.length).reduce(_ + _))
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def at(vertex: Vertex): Vector[Vertex] =
|
||||
neighbors.get(vertex).getOrElse(Vector.empty)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def contains(vertex: Vertex): Boolean =
|
||||
neighbors.contains(vertex)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def incoming(vertex: Vertex): Vector[Vertex] =
|
||||
neighbors.view
|
||||
.filter {
|
||||
// ignore the neighbors of the input vertex
|
||||
// select those that reference the input
|
||||
case (key, values) => (key != vertex) && values.contains(vertex)
|
||||
}
|
||||
.map(_._1)
|
||||
.toVector
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toEdges(): Vector[Edge] =
|
||||
neighbors.view.flatMap { case (from, tos) =>
|
||||
tos.map(to => Edge(from, to)).distinct
|
||||
}.toVector
|
||||
|
||||
object MappedAdjacency:
|
||||
|
||||
final val Empty: Adjacency = new MappedAdjacency(Size.Zero, Map.empty)
|
||||
|
||||
def single(vertex: Vertex): Adjacency =
|
||||
new MappedAdjacency(Size.One, Map(vertex -> Vector.empty))
|
||||
|
||||
/** Validate the given mapped neighbors. Enforces that all neighbors of some
|
||||
* vertex _must_ be keys of the map -- neighbors can only be vertices that
|
||||
* are part of the same graph.
|
||||
*
|
||||
* Throws an exception if the input is invalid.
|
||||
*
|
||||
* @param neighbors
|
||||
* The mapped neighbors.
|
||||
* @return
|
||||
* The new [[Adjacency]].
|
||||
*/
|
||||
def validate(
|
||||
neighbors: Map[Vertex, Vector[Vertex]]
|
||||
): Adjacency =
|
||||
val vertices = neighbors.keySet
|
||||
val _ = neighbors.foreach { case (v, ns) =>
|
||||
ns.foreach { candidate =>
|
||||
if !vertices.contains(candidate) then
|
||||
throw GraphException.NeighborNotContainedByAdjacency(
|
||||
candidate = candidate,
|
||||
vertex = v
|
||||
)
|
||||
else ()
|
||||
}
|
||||
}
|
||||
new MappedAdjacency(Size.of(neighbors), neighbors)
|
||||
|
||||
/** Instantiate a new [[MappedAdjacency]] based on the given edges.
|
||||
*
|
||||
* @param edges
|
||||
* The edges that represent the adjacency.
|
||||
* @return
|
||||
* the new adjacency instance.
|
||||
*/
|
||||
def fromEdges(
|
||||
edges: Iterable[Edge]
|
||||
): Adjacency =
|
||||
val acc = scala.collection.mutable.Map.empty[Vertex, ListBuffer[Vertex]]
|
||||
edges.foreach { edge =>
|
||||
acc.getOrElseUpdate(edge.from, ListBuffer.empty).addOne(edge.to)
|
||||
}
|
||||
val neighbors = acc.map { case (v, ns) => v -> ns.toVector }.toMap
|
||||
new MappedAdjacency(Size.of(neighbors), neighbors)
|
||||
|
||||
end MappedAdjacency
|
||||
|
|
|
|||
|
|
@ -105,6 +105,19 @@ object Edge:
|
|||
v2: Int
|
||||
): Edge = Edge(Vertex(v1), Vertex(v2))
|
||||
|
||||
/** Instantiate a new Edge.
|
||||
*
|
||||
* Throws an exception if the given vertices are the same or are not valid
|
||||
* [[Vertex]] values.
|
||||
*
|
||||
* @param pair
|
||||
* The pair to convert to an edge.
|
||||
* @return
|
||||
* The new edge.
|
||||
*/
|
||||
def fromPair(pair: (Int, Int)): Edge =
|
||||
Edge(Vertex(pair._1), Vertex(pair._2))
|
||||
|
||||
/** Instantiate a list of Edge.
|
||||
*
|
||||
* @param edges
|
||||
|
|
|
|||
|
|
@ -46,11 +46,6 @@ trait Graph:
|
|||
*/
|
||||
def isSingle: Boolean = numberOfVertices == Size.One
|
||||
|
||||
/** @return
|
||||
* The roots that should be used for traversal operations.
|
||||
*/
|
||||
def selectRoots(): Vector[Vertex]
|
||||
|
||||
/** Get the neighbors for any given vertex.
|
||||
*
|
||||
* @param vertex
|
||||
|
|
|
|||
|
|
@ -31,4 +31,22 @@ object GraphException:
|
|||
but that root exceeds the graph bound of '$numberOfVertices'
|
||||
vertices.""".stripMargin
|
||||
|
||||
case class AdjacencyDoesNotContainVertices(
|
||||
vertices: Iterable[Vertex],
|
||||
numberOfVertices: Size
|
||||
) extends GraphException:
|
||||
|
||||
override def getMessage(): String =
|
||||
s"""The following vertices were not found within an Adjacency of size
|
||||
'$numberOfVertices': [${vertices.mkString(", ")}]""".stripMargin
|
||||
|
||||
case class NeighborNotContainedByAdjacency(
|
||||
candidate: Vertex,
|
||||
vertex: Vertex
|
||||
) extends GraphException:
|
||||
|
||||
override def getMessage(): String =
|
||||
s"""Attempted to reference neighbor '$candidate' of vertex '$vertex', but
|
||||
'$candidate' is not a valid vertex in the adjacency.""".stripMargin
|
||||
|
||||
end GraphException
|
||||
|
|
|
|||
|
|
@ -159,16 +159,18 @@ object GraphTraversal:
|
|||
val s = Stack.empty[Vertex]
|
||||
val discovered = Array.fill(graph.numberOfVertices.value)(false)
|
||||
|
||||
graph.selectRoots().foreach { root =>
|
||||
val _ = s.push(root)
|
||||
while !s.isEmpty
|
||||
do
|
||||
val v = s.pop()
|
||||
if !discovered(v.ordinal) then
|
||||
val _ = output.addOne(visit(v, graph.data(v.ordinal)))
|
||||
discovered(v.ordinal) = true
|
||||
graph.neighbors(v).foreach(w => s.push(w))
|
||||
else ()
|
||||
graph.view.foreach { root =>
|
||||
if !discovered(root.ordinal) then
|
||||
val _ = s.push(root)
|
||||
while !s.isEmpty
|
||||
do
|
||||
val v = s.pop()
|
||||
if !discovered(v.ordinal) then
|
||||
val _ = output.addOne(visit(v, graph.data(v.ordinal)))
|
||||
discovered(v.ordinal) = true
|
||||
val _ = graph.neighbors(v).reverseIterator.foreach(w => s.push(w))
|
||||
else ()
|
||||
else ()
|
||||
}
|
||||
|
||||
output.toList
|
||||
|
|
@ -283,20 +285,24 @@ object GraphTraversal:
|
|||
): Unit =
|
||||
val q = Queue.empty[Vertex]
|
||||
val visited = Array.fill(graph.numberOfVertices.value)(false)
|
||||
val _ = graph.selectRoots().foreach(q.enqueue)
|
||||
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
val _ = visit(v, graph.data(v.ordinal))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
graph.view.foreach { root =>
|
||||
if !visited(root.ordinal) then
|
||||
val _ = q.enqueue(root)
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
val _ = visit(v, graph.data(v.ordinal))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
else ()
|
||||
}
|
||||
else ()
|
||||
}
|
||||
()
|
||||
else ()
|
||||
()
|
||||
}
|
||||
|
||||
def bfs[A, Out](
|
||||
graph: AnyGraphWithData[A],
|
||||
|
|
@ -305,19 +311,23 @@ object GraphTraversal:
|
|||
val output = ListBuffer.empty[Out]
|
||||
val q = Queue.empty[Vertex]
|
||||
val visited = Array.fill(graph.numberOfVertices.value)(false)
|
||||
val _ = graph.selectRoots().foreach(q.enqueue)
|
||||
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
val _ = output.addOne(visit(v, graph.data(v.ordinal)))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
graph.view.foreach { root =>
|
||||
if !visited(root.ordinal) then
|
||||
val _ = q.enqueue(root)
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
val _ = output.addOne(visit(v, graph.data(v.ordinal)))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
else ()
|
||||
}
|
||||
else ()
|
||||
}
|
||||
else ()
|
||||
}
|
||||
|
||||
output.toList
|
||||
|
||||
|
|
@ -329,19 +339,22 @@ object GraphTraversal:
|
|||
var acc = initial
|
||||
val q = Queue.empty[Vertex]
|
||||
val visited = Array.fill(graph.numberOfVertices.value)(false)
|
||||
val _ = graph.selectRoots().foreach(q.enqueue)
|
||||
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
acc = f(acc, graph.data(v.ordinal))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
graph.view.foreach { root =>
|
||||
if !visited(root.ordinal) then
|
||||
while !q.isEmpty
|
||||
do
|
||||
val v = q.dequeue()
|
||||
if !visited(v.ordinal) then
|
||||
acc = f(acc, graph.data(v.ordinal))
|
||||
visited(v.ordinal) = true
|
||||
graph.neighbors(v).foreach { neighbor =>
|
||||
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
|
||||
else ()
|
||||
}
|
||||
else ()
|
||||
}
|
||||
else ()
|
||||
}
|
||||
|
||||
acc
|
||||
|
||||
|
|
|
|||
|
|
@ -15,25 +15,6 @@ class UndirectedGraph(
|
|||
*/
|
||||
final override val disposition: GraphDisposition = GraphDisposition.Undirected
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def selectRoots(): Vector[Vertex] = roots
|
||||
|
||||
/** The lazily calculated roots of this graph.
|
||||
*
|
||||
* In an undirected graph, the roots are calculated by selecting an arbitrary
|
||||
* vertex from each connected component in the graph.
|
||||
*/
|
||||
lazy val roots: Vector[Vertex] =
|
||||
calculateRoots()
|
||||
|
||||
private def calculateRoots(): Vector[Vertex] =
|
||||
if numberOfVertices == Size.Zero then Vector.empty
|
||||
else oneFromEachConnectedComponent().toVector
|
||||
|
||||
private def oneFromEachConnectedComponent(): List[Vertex] =
|
||||
ConnectedComponent.findAll(this).map(_.root)
|
||||
|
||||
end UndirectedGraph
|
||||
|
||||
object UndirectedGraph:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package gs.graph.v0.data
|
|||
import gs.graph.v0.Adjacency
|
||||
import gs.graph.v0.GraphException
|
||||
import gs.graph.v0.Size
|
||||
import gs.graph.v0.Vertex
|
||||
import gs.graph.v0.directed.Dag
|
||||
|
||||
/** Specialization of [[Dag]] that associates _data_ with _each [[Vertex]]_.
|
||||
|
|
@ -11,9 +10,8 @@ import gs.graph.v0.directed.Dag
|
|||
final class DataDag[A] private (
|
||||
val data: Vector[A],
|
||||
n: Size,
|
||||
a: Adjacency,
|
||||
r: Vector[Vertex]
|
||||
) extends Dag(n, a, r)
|
||||
a: Adjacency
|
||||
) extends Dag(n, a)
|
||||
with AnyGraphWithData[A]
|
||||
|
||||
object DataDag:
|
||||
|
|
@ -25,8 +23,7 @@ object DataDag:
|
|||
new DataDag[A](
|
||||
Vector.empty,
|
||||
Size.Zero,
|
||||
Adjacency.Empty,
|
||||
Vector.empty
|
||||
Adjacency.Empty
|
||||
)
|
||||
|
||||
/** Instantiate a new [[DataDag]] given an input [[Dag]] and some data.
|
||||
|
|
@ -54,6 +51,5 @@ object DataDag:
|
|||
new DataDag(
|
||||
data,
|
||||
dag.numberOfVertices,
|
||||
dag.adjacency,
|
||||
dag.roots
|
||||
dag.adjacency
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package gs.graph.v0.data
|
|||
import gs.graph.v0.Adjacency
|
||||
import gs.graph.v0.GraphException
|
||||
import gs.graph.v0.Size
|
||||
import gs.graph.v0.Vertex
|
||||
import gs.graph.v0.directed.Digraph
|
||||
|
||||
/** Specialization of [[Digraph]] that associates _data_ with _each [[Vertex]]_.
|
||||
|
|
@ -11,9 +10,8 @@ import gs.graph.v0.directed.Digraph
|
|||
final class DataDigraph[A] private (
|
||||
val data: Vector[A],
|
||||
n: Size,
|
||||
a: Adjacency,
|
||||
r: Vector[Vertex]
|
||||
) extends Digraph(n, a, r)
|
||||
a: Adjacency
|
||||
) extends Digraph(n, a)
|
||||
with AnyGraphWithData[A]
|
||||
|
||||
object DataDigraph:
|
||||
|
|
@ -25,8 +23,7 @@ object DataDigraph:
|
|||
new DataDigraph[A](
|
||||
Vector.empty,
|
||||
Size.Zero,
|
||||
Adjacency.Empty,
|
||||
Vector.empty
|
||||
Adjacency.Empty
|
||||
)
|
||||
|
||||
/** Instantiate a new [[DataDigraph]] given an input [[Digraph]] and some
|
||||
|
|
@ -55,6 +52,5 @@ object DataDigraph:
|
|||
new DataDigraph(
|
||||
data,
|
||||
digraph.numberOfVertices,
|
||||
digraph.adjacency,
|
||||
digraph.roots
|
||||
digraph.adjacency
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package gs.graph.v0.directed
|
|||
import gs.graph.v0.Adjacency
|
||||
import gs.graph.v0.GraphError
|
||||
import gs.graph.v0.Size
|
||||
import gs.graph.v0.Vertex
|
||||
|
||||
/** Directed Acyclic Graph (DAG): Represents a [[Digraph]] with no cycles.
|
||||
*
|
||||
|
|
@ -12,21 +11,20 @@ import gs.graph.v0.Vertex
|
|||
*/
|
||||
class Dag protected (
|
||||
n: Size,
|
||||
a: Adjacency,
|
||||
r: Vector[Vertex]
|
||||
) extends Digraph(n, a, r)
|
||||
a: Adjacency
|
||||
) extends Digraph(n, a)
|
||||
|
||||
object Dag:
|
||||
|
||||
/** The empty DAG.
|
||||
*/
|
||||
final val Empty: Dag =
|
||||
new Dag(Size.Zero, Adjacency.Empty, Vector.empty)
|
||||
new Dag(Size.Zero, Adjacency.Empty)
|
||||
|
||||
/** DAG with a single vertex.
|
||||
*/
|
||||
final val Single: Dag =
|
||||
new Dag(Size.One, Adjacency.Single, Vector(Vertex.Zero))
|
||||
new Dag(Size.One, Adjacency.Single)
|
||||
|
||||
given CanEqual[Dag, Dag] = CanEqual.derived
|
||||
given CanEqual[Dag, Digraph] = CanEqual.derived
|
||||
|
|
@ -42,7 +40,7 @@ object Dag:
|
|||
def validate(digraph: Digraph): Either[GraphError, Dag] =
|
||||
if !Digraph.hasCycle(digraph) then
|
||||
Right(
|
||||
new Dag(digraph.numberOfVertices, digraph.adjacency, digraph.roots)
|
||||
new Dag(digraph.numberOfVertices, digraph.adjacency)
|
||||
)
|
||||
else Left(GraphError.CycleFound)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,22 +13,15 @@ import gs.graph.v0.Vertex
|
|||
* The number of [[Vertex]] present in this graph.
|
||||
* @param adjacency
|
||||
* The [[Adjacency]] that describes this graph.
|
||||
* @param roots
|
||||
* Complete collection of root vertices.
|
||||
*/
|
||||
class Digraph(
|
||||
val numberOfVertices: Size,
|
||||
val adjacency: Adjacency,
|
||||
val roots: Vector[Vertex]
|
||||
val adjacency: Adjacency
|
||||
) extends Graph:
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override val disposition: GraphDisposition = GraphDisposition.Directed
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def selectRoots(): Vector[Vertex] = roots
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def equals(that: Any): Boolean =
|
||||
|
|
@ -52,13 +45,13 @@ object Digraph:
|
|||
* An empty [[Digraph]].
|
||||
*/
|
||||
final val Empty: Digraph =
|
||||
new Digraph(Size.Zero, Adjacency.Empty, Vector.empty)
|
||||
new Digraph(Size.Zero, Adjacency.Empty)
|
||||
|
||||
/** @return
|
||||
* A [[Digraph]] with one vertex.
|
||||
*/
|
||||
final val Single: Digraph =
|
||||
new Digraph(Size.One, Adjacency.Single, Vector(Vertex.Zero))
|
||||
new Digraph(Size.One, Adjacency.Single)
|
||||
|
||||
def fromEdges(
|
||||
numberOfVertices: Size,
|
||||
|
|
@ -66,8 +59,7 @@ object Digraph:
|
|||
): Digraph =
|
||||
new Digraph(
|
||||
numberOfVertices = numberOfVertices,
|
||||
adjacency = Adjacency.fromEdges(numberOfVertices, edges),
|
||||
roots = findRootsForDirectedEdges(numberOfVertices, edges)
|
||||
adjacency = Adjacency.fromEdges(numberOfVertices, edges)
|
||||
)
|
||||
|
||||
/** Instantiate a new [[Graph]] from the given [[Adjacency]].
|
||||
|
|
@ -82,37 +74,9 @@ object Digraph:
|
|||
): Digraph =
|
||||
new Digraph(
|
||||
numberOfVertices = adjacency.numberOfVertices,
|
||||
adjacency = adjacency,
|
||||
roots = adjacency.roots
|
||||
adjacency = adjacency
|
||||
)
|
||||
|
||||
/** Find all roots for the given collection of [[Edge]].
|
||||
*
|
||||
* This function assumes directed edges.
|
||||
*
|
||||
* @param numberOfVertices
|
||||
* The number of vertices in the graph represented by the given edges.
|
||||
* @param edges
|
||||
* The collection of edges to analyze.
|
||||
* @return
|
||||
* The vector of roots for the given edges.
|
||||
*/
|
||||
def findRootsForDirectedEdges(
|
||||
numberOfVertices: Size,
|
||||
edges: Iterable[Edge]
|
||||
): Vector[Vertex] =
|
||||
// Count of incoming relationships.
|
||||
val counts = Array.fill(numberOfVertices.value)(0)
|
||||
edges.foreach(edge => counts(edge.to.ordinal) += 1)
|
||||
|
||||
// If there are any vertices with no incoming connections, those are roots.
|
||||
// Note that these may be completely disconnected.
|
||||
counts.zipWithIndex
|
||||
.filter(_._1 == 0)
|
||||
.map { case (_, index) => Vertex(index) }
|
||||
.toVector
|
||||
.distinct
|
||||
|
||||
/** Determine whether the given [[Digraph]] has any cycles.
|
||||
*
|
||||
* See: [[Dag]]
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
package gs.graph.v0.directed
|
||||
|
||||
import gs.graph.v0.Adjacency
|
||||
import gs.graph.v0.Edge
|
||||
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,
|
||||
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(
|
||||
new SingleRootDigraph(
|
||||
dg.numberOfVertices,
|
||||
dg.adjacency,
|
||||
dg.roots(0)
|
||||
)
|
||||
)
|
||||
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],
|
||||
root: Vertex
|
||||
): SingleRootDigraph =
|
||||
if root < numberOfVertices then
|
||||
new SingleRootDigraph(
|
||||
numberOfVertices,
|
||||
Adjacency.fromEdges(numberOfVertices, edges),
|
||||
root
|
||||
)
|
||||
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]
|
||||
): Option[SingleRootDigraph] =
|
||||
val roots = Digraph.findRootsForDirectedEdges(numberOfVertices, edges)
|
||||
if roots.size == 1 then
|
||||
Some(
|
||||
new SingleRootDigraph(
|
||||
numberOfVertices,
|
||||
Adjacency.fromEdges(numberOfVertices, edges),
|
||||
roots(0)
|
||||
)
|
||||
)
|
||||
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
|
||||
): SingleRootDigraph =
|
||||
if root < adjacency.numberOfVertices then
|
||||
new SingleRootDigraph(
|
||||
adjacency.numberOfVertices,
|
||||
adjacency,
|
||||
root
|
||||
)
|
||||
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.roots
|
||||
if roots.size == 1 then
|
||||
Some(
|
||||
new SingleRootDigraph(
|
||||
adjacency.numberOfVertices,
|
||||
adjacency,
|
||||
roots(0)
|
||||
)
|
||||
)
|
||||
else None
|
||||
|
||||
end SingleRootDigraph
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package gs.graph.v0.directed
|
||||
|
||||
import gs.graph.v0.Adjacency
|
||||
import gs.graph.v0.Edge
|
||||
import gs.graph.v0.MappedAdjacency
|
||||
import gs.graph.v0.Vertex
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.collection.mutable.Stack
|
||||
|
|
@ -10,49 +13,50 @@ import scala.collection.mutable.Stack
|
|||
*
|
||||
* This class is used with [[Digraph]]. See [[gs.graph.v0.ConnectedComponent]]
|
||||
* for general connected components.
|
||||
*
|
||||
* TODO: ADD EDGES
|
||||
*/
|
||||
final class StronglyConnectedComponent private (val value: Set[Vertex]):
|
||||
final class StronglyConnectedComponent private (
|
||||
val vertices: Set[Vertex],
|
||||
val adjacency: Adjacency
|
||||
):
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def equals(obj: Any): Boolean =
|
||||
obj match
|
||||
case other: StronglyConnectedComponent => value.equals(other.value)
|
||||
case _ => false
|
||||
case other: StronglyConnectedComponent =>
|
||||
vertices.equals(other.vertices) && adjacency.equals(other.adjacency)
|
||||
case _ => false
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def hashCode(): Int = value.hashCode()
|
||||
override def hashCode(): Int = adjacency.hashCode()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toString(): String =
|
||||
val sb = StringBuilder()
|
||||
sb.append("[")
|
||||
sb.append(value.mkString(","))
|
||||
sb.append("]")
|
||||
sb.append("entries:[\n")
|
||||
sb.append(
|
||||
adjacency.entries
|
||||
.map { case (v, ns) => s"$v -> ${ns.mkString(",")}" }
|
||||
.mkString("\n")
|
||||
)
|
||||
sb.append("\n]")
|
||||
sb.toString()
|
||||
|
||||
/** @return
|
||||
* The number of vertices contained by this SCC.
|
||||
*/
|
||||
def size: Int = value.size
|
||||
lazy val size: Int = vertices.size
|
||||
|
||||
/** @return
|
||||
* True if this SCC contains a single vertex.
|
||||
*/
|
||||
def isSingle: Boolean = value.size == 1
|
||||
|
||||
/** @return
|
||||
* The set of vertices in the SCC.
|
||||
*/
|
||||
def vertices: Set[Vertex] = value
|
||||
def isSingle: Boolean = size == 1
|
||||
|
||||
/** Retrieve an arbitrary SCC member as the root vertex.
|
||||
*/
|
||||
lazy val root: Vertex = value.toList.head
|
||||
lazy val root: Vertex = vertices.toList.head
|
||||
|
||||
end StronglyConnectedComponent
|
||||
|
||||
|
|
@ -67,19 +71,29 @@ object StronglyConnectedComponent:
|
|||
* @return
|
||||
* The new SCC.
|
||||
*/
|
||||
def apply(value: Vertex*): StronglyConnectedComponent =
|
||||
if value.isEmpty then
|
||||
def apply(connections: Edge*): StronglyConnectedComponent =
|
||||
if connections.isEmpty then
|
||||
throw new IllegalArgumentException(
|
||||
"Empty strongly connected components (SCC) are not allowed."
|
||||
)
|
||||
else new StronglyConnectedComponent(value.toSet)
|
||||
else
|
||||
val adj = MappedAdjacency.fromEdges(connections)
|
||||
new StronglyConnectedComponent(
|
||||
vertices = adj.vertices.toSet,
|
||||
adjacency = adj
|
||||
)
|
||||
|
||||
def of(value: Int*): StronglyConnectedComponent =
|
||||
def of(value: (Int, Int)*): StronglyConnectedComponent =
|
||||
if value.isEmpty then
|
||||
throw new IllegalArgumentException(
|
||||
"Empty strongly connected components (SCC) are not allowed."
|
||||
)
|
||||
else new StronglyConnectedComponent(value.map(Vertex.apply).toSet)
|
||||
else
|
||||
val adj = MappedAdjacency.fromEdges(value.map(Edge.fromPair))
|
||||
new StronglyConnectedComponent(
|
||||
vertices = adj.vertices.toSet,
|
||||
adjacency = adj
|
||||
)
|
||||
|
||||
/** Instantiate a new SCC that contains a single [[Vertex]].
|
||||
*
|
||||
|
|
@ -89,7 +103,10 @@ object StronglyConnectedComponent:
|
|||
* The new SCC.
|
||||
*/
|
||||
def single(value: Vertex): StronglyConnectedComponent =
|
||||
new StronglyConnectedComponent(Set(value))
|
||||
new StronglyConnectedComponent(
|
||||
vertices = Set(value),
|
||||
adjacency = MappedAdjacency.single(value)
|
||||
)
|
||||
|
||||
given CanEqual[StronglyConnectedComponent, StronglyConnectedComponent] =
|
||||
CanEqual.derived
|
||||
|
|
@ -201,9 +218,17 @@ object StronglyConnectedComponent:
|
|||
sccVertex = s.pop()
|
||||
|
||||
scc.addOne(sccVertex)
|
||||
if !scc.isEmpty then
|
||||
output.addOne(new StronglyConnectedComponent(scc.toSet))
|
||||
else println("ERROR")
|
||||
|
||||
// These accumulated vertices make up the SCC.
|
||||
val vertices = scc.toSet
|
||||
|
||||
output.addOne(
|
||||
new StronglyConnectedComponent(
|
||||
vertices = scc.toSet,
|
||||
// Select the subset of the adjacency which is relevant to the SCC.
|
||||
adjacency = g.adjacency.subset(vertices)
|
||||
)
|
||||
)
|
||||
else ()
|
||||
|
||||
/** Mutable state wrapper around an integer.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ class UndirectedGraphTests extends FunSuite:
|
|||
assertEquals(g.numberOfVertices, Size.Zero)
|
||||
assertEquals(g.adjacency, Adjacency.Empty)
|
||||
assertEquals(g.disposition, GraphDisposition.Undirected)
|
||||
assertEquals(g.selectRoots(), Vector.empty)
|
||||
}
|
||||
|
||||
test("should construct a graph based on # of edges and edge list") {
|
||||
|
|
@ -24,7 +23,6 @@ class UndirectedGraphTests extends FunSuite:
|
|||
assertEquals(g.numberOfVertices, N)
|
||||
assertEquals(g.neighbors(Vertex(0)), Vector(Vertex(1), Vertex(2)))
|
||||
assertEquals(g.neighbors(Vertex(4)), Vector.empty)
|
||||
assertEquals(g.selectRoots(), Vector(Vertex.Zero))
|
||||
}
|
||||
|
||||
test("should construct a graph based on edge list") {
|
||||
|
|
@ -38,7 +36,6 @@ class UndirectedGraphTests extends FunSuite:
|
|||
assertEquals(g.numberOfVertices, N)
|
||||
assertEquals(g.neighbors(Vertex(0)), Vector(Vertex(1), Vertex(2)))
|
||||
assertEquals(g.neighbors(Vertex(4)), Vector.empty)
|
||||
assertEquals(g.selectRoots(), Vector(Vertex.Zero))
|
||||
}
|
||||
|
||||
test("should calculate roots for each connected component") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue