WIP: More fixes and traversal fully tested for non-enriched graphs.

This commit is contained in:
Pat Garrity 2026-05-31 16:09:42 -05:00
parent 117bff5242
commit 180990c934
Signed by: pfm
GPG key ID: 0DC16BCA24B270C4
7 changed files with 614 additions and 93 deletions

View file

@ -1,6 +1,7 @@
package gs.graph.v0
import gs.graph.v0.directed.Digraph
import scala.collection.IndexedSeqView
/** Graph representation based on an adjacency list.
*
@ -29,6 +30,12 @@ trait Graph:
*/
def numberOfVertices: Size
/** @return
* View over all [[Vertex]] values in this graph.
*/
def view: IndexedSeqView[Vertex] =
(0 until numberOfVertices.value).view.map(Vertex(_))
/** @return
* True if this graph has no vertices, false otherwise.
*/

View file

@ -7,13 +7,19 @@ package gs.graph.v0
*/
sealed abstract class GraphDisposition(val name: String):
/** @inheritDocs
*/
override def equals(that: Any): Boolean =
that match
case other: GraphDisposition => name == other.name
case _ => false
/** @inheritDocs
*/
override def hashCode(): Int = name.hashCode()
/** @inheritDocs
*/
override def toString(): String = name
object GraphDisposition:
@ -28,4 +34,17 @@ object GraphDisposition:
*/
case object Undirected extends GraphDisposition("undirected")
/** Parse the given string as a [[GraphDisposition]].
*
* @param candidate
* The string to validate.
* @return
* The parsed [[GraphDisposition]], or `None` if the input does not
* represent a valid disposition.
*/
def parse(candidate: String): Option[GraphDisposition] =
if candidate.equalsIgnoreCase(Directed.name) then Some(Directed)
else if candidate.equalsIgnoreCase(Undirected.name) then Some(Undirected)
else None
end GraphDisposition

View file

@ -12,9 +12,6 @@ object GraphTraversal:
/** Depth-first search that executes a side-effecting function on each
* [[Vertex]]. This function will operate on _any_ [[Graph]].
*
* This implementation selects the first [[Vertex]] as an arbitrary starting
* point.
*
* @param graph
* The input [[Graph]] on which to run DFS.
* @param visit
@ -27,25 +24,23 @@ 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 _ = visit(v)
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 _ = visit(v)
discovered(v.ordinal) = true
val _ = graph.neighbors(v).reverseIterator.foreach(s.push)
else ()
else ()
}
/** Depth-first search that executes a function on each [[Vertex]] to produce
* some output. This function will operate on _any_ [[Graph]].
*
* This implementation selects the first [[Vertex]] as an arbitrary starting
* point.
*
* @param graph
* The input [[Graph]] on which to run DFS.
* @param visit
@ -61,20 +56,35 @@ 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))
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))
discovered(v.ordinal) = true
val _ = graph.neighbors(v).reverseIterator.foreach(s.push)
else ()
else ()
}
output.toList
/** Depth-first search that executes a function on each [[Vertex]] in the
* context of some accumulator value to produce some accumulated output. This
* function will operate on _any_ [[Graph]].
*
* @param graph
* The input [[Graph]] on which to run DFS.
* @param initial
* The initial value.
* @param f
* The function to apply to each vertex in the context of an accumulator.
* @return
* The accumulated value.
*/
def dfsFold[Acc](
graph: Graph,
initial: Acc,
@ -84,16 +94,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
acc = f(acc, v)
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
acc = f(acc, v)
discovered(v.ordinal) = true
val _ = graph.neighbors(v).reverseIterator.foreach(s.push)
else ()
else ()
}
acc
@ -101,9 +113,6 @@ object GraphTraversal:
/** Depth-first search that executes a side-effecting function on each
* [[Vertex]], accepting the data stored for that [[Vertex]] as input.
*
* This implementation performs DFS for _each root_ in the input
* [[AnyGraphWithData]].
*
* @param graph
* The input [[Graph]] on which to run DFS.
* @param visit
@ -116,16 +125,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 _ = 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 _ = visit(v, graph.data(v.ordinal))
discovered(v.ordinal) = true
val _ = graph.neighbors(v).reverseIterator.foreach(s.push)
else ()
else ()
}
()
@ -133,9 +144,6 @@ object GraphTraversal:
/** Depth-first search that executes a function on each [[Vertex]] to produce
* some output, accepting the data stored for that [[Vertex]] as input.
*
* This implementation performs DFS for _each root_ in the input
* [[AnyGraphWithData]].
*
* @param graph
* The input [[Graph]] on which to run DFS.
* @param visit
@ -174,16 +182,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
acc = f(acc, 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
acc = f(acc, graph.data(v.ordinal))
discovered(v.ordinal) = true
val _ = graph.neighbors(v).reverseIterator.foreach(s.push)
else ()
else ()
}
acc
@ -194,19 +204,23 @@ 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)
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)
visited(v.ordinal) = true
graph.neighbors(v).foreach { neighbor =>
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
else ()
}
else ()
}
else ()
}
def bfs[Out](
graph: Graph,
@ -215,19 +229,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))
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))
visited(v.ordinal) = true
graph.neighbors(v).foreach { neighbor =>
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
else ()
}
else ()
}
else ()
}
output.toList
@ -239,19 +257,23 @@ 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, v)
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
acc = f(acc, v)
visited(v.ordinal) = true
graph.neighbors(v).foreach { neighbor =>
if !visited(neighbor.ordinal) then q.enqueue(neighbor)
else ()
}
else ()
}
else ()
}
acc

View file

@ -0,0 +1,200 @@
package gs.graph.v0
import gs.graph.v0.BfsTraversalTests.Counter
import gs.graph.v0.BfsTraversalTests.Tracker
import gs.graph.v0.directed.Digraph
import munit.*
import scala.collection.mutable.ListBuffer
class BfsTraversalTests extends FunSuite:
test("should perform BFS on an empty graph") {
val c1 = new Counter
val _ = GraphTraversal.bfs(graph = UndirectedGraph.Empty, visit = c1.visit)
assertEquals(c1.count, 0)
val c2 = new Counter
val _ = GraphTraversal.bfs(graph = Digraph.Empty, visit = c2.visit)
assertEquals(c2.count, 0)
}
test("should perform BFS on a graph of size 1") {
val c1 = new Counter
val _ = GraphTraversal.bfs(graph = UndirectedGraph.Single, visit = c1.visit)
assertEquals(c1.count, 1)
val c2 = new Counter
val _ = GraphTraversal.bfs(graph = Digraph.Single, visit = c2.visit)
assertEquals(c2.count, 1)
}
test("should perform BFS on a complex directed graph") {
val n = Size(6)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
1 -> 2,
2 -> 1,
0 -> 3,
3 -> 2,
4 -> 5
)
val expectedVisitOrder = List(0, 1, 3, 2, 4, 5)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.bfs(g, t.visit)
val o1 = GraphTraversal.bfs(g, v => v.ordinal)
val o2 = GraphTraversal.bfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform BFS on a DAG") {
val n = Size(4)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
0 -> 2,
1 -> 3,
2 -> 3
)
val expectedVisitOrder = List(0, 1, 2, 3)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.bfs(g, t.visit)
val o1 = GraphTraversal.bfs(g, v => v.ordinal)
val o2 = GraphTraversal.bfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform BFS on a strongly connected graph") {
val n = Size(3)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
1 -> 2,
2 -> 0
)
val expectedVisitOrder = List(0, 1, 2)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.bfs(g, t.visit)
val o1 = GraphTraversal.bfs(g, v => v.ordinal)
val o2 = GraphTraversal.bfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform BFS on a line") {
val n = Size(4)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
1 -> 2,
2 -> 3
)
val expectedVisitOrder = List(0, 1, 2, 3)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.bfs(g, t.visit)
val o1 = GraphTraversal.bfs(g, v => v.ordinal)
val o2 = GraphTraversal.bfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform BFS on a larger complex digraph") {
val n = Size(10)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
1 -> 2,
2 -> 3,
3 -> 6,
4 -> 5,
5 -> 6,
6 -> 7,
7 -> 8,
8 -> 9,
9 -> 6,
6 -> 8
)
val expectedVisitOrder = List(0, 1, 2, 3, 6, 7, 8, 9, 4, 5)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.bfs(g, t.visit)
val o1 = GraphTraversal.bfs(g, v => v.ordinal)
val o2 = GraphTraversal.bfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
object BfsTraversalTests:
trait BasicVisitor:
def visit(v: Vertex): Unit
class Counter extends BasicVisitor:
var count: Int = 0
def visit(v: Vertex): Unit =
count = count + 1
()
end Counter
class Tracker extends BasicVisitor:
private val output: ListBuffer[Int] = ListBuffer.empty
def snapshot(): List[Int] = output.toList
def visit(v: Vertex): Unit =
val _ = output.addOne(v.ordinal)
()
end Tracker
end BfsTraversalTests

View file

@ -0,0 +1,203 @@
package gs.graph.v0
import gs.graph.v0.DfsTraversalTests.Counter
import gs.graph.v0.DfsTraversalTests.Tracker
import gs.graph.v0.directed.Digraph
import munit.*
import scala.collection.mutable.ListBuffer
class DfsTraversalTests extends FunSuite:
test("should perform DFS on an empty graph") {
val c1 = new Counter
val _ = GraphTraversal.dfs(graph = UndirectedGraph.Empty, visit = c1.visit)
assertEquals(c1.count, 0)
val c2 = new Counter
val _ = GraphTraversal.dfs(graph = Digraph.Empty, visit = c2.visit)
assertEquals(c2.count, 0)
}
test("should perform DFS on a graph of size 1") {
val c1 = new Counter
val _ = GraphTraversal.dfs(graph = UndirectedGraph.Single, visit = c1.visit)
assertEquals(c1.count, 1)
val c2 = new Counter
val _ = GraphTraversal.dfs(graph = Digraph.Single, visit = c2.visit)
assertEquals(c2.count, 1)
}
test("should perform DFS on a complex directed graph") {
// This test case includes:
// - Cycles
// - Multi-Root (0 and 4 are the roots)
val n = Size(6)
val g = Graph.directed(
numberOfVertices = n,
edges = (0, 1),
(1, 2),
(2, 1),
(0, 3),
(3, 2),
(4, 5)
)
val expectedVisitOrder = List(0, 1, 2, 3, 4, 5)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.dfs(g, t.visit)
val o1 = GraphTraversal.dfs(g, v => v.ordinal)
val o2 = GraphTraversal.dfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform DFS on a DAG") {
val n = Size(4)
val g = Graph.directed(
numberOfVertices = n,
edges = (0, 1),
(0, 2),
(1, 3),
(2, 3)
)
val expectedVisitOrder = List(0, 1, 3, 2)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.dfs(g, t.visit)
val o1 = GraphTraversal.dfs(g, v => v.ordinal)
val o2 = GraphTraversal.dfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform DFS on a strongly connected graph") {
val n = Size(3)
val g = Graph.directed(
numberOfVertices = n,
edges = (0, 1),
(1, 2),
(2, 0)
)
val expectedVisitOrder = List(0, 1, 2)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.dfs(g, t.visit)
val o1 = GraphTraversal.dfs(g, v => v.ordinal)
val o2 = GraphTraversal.dfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform DFS on a line") {
val n = Size(4)
val g = Graph.directed(
numberOfVertices = n,
edges = (0, 1),
(1, 2),
(2, 3)
)
val expectedVisitOrder = List(0, 1, 2, 3)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.dfs(g, t.visit)
val o1 = GraphTraversal.dfs(g, v => v.ordinal)
val o2 = GraphTraversal.dfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
test("should perform DFS on a larger complex digraph") {
val n = Size(10)
val g = Graph.directed(
numberOfVertices = n,
edges = 0 -> 1,
1 -> 2,
2 -> 3,
3 -> 6,
4 -> 5,
5 -> 6,
6 -> 7,
7 -> 8,
8 -> 9,
9 -> 6,
6 -> 8
)
val expectedVisitOrder = List(0, 1, 2, 3, 6, 7, 8, 9, 4, 5)
val t = new Tracker
val b = ListBuffer.empty[Int]
val _ = GraphTraversal.dfs(g, t.visit)
val o1 = GraphTraversal.dfs(g, v => v.ordinal)
val o2 = GraphTraversal.dfsFold(
g,
b,
(
acc,
vx
) => acc.addOne(vx.ordinal)
)
assertEquals(t.snapshot(), expectedVisitOrder)
assertEquals(o1, expectedVisitOrder)
assertEquals(o2.toList, expectedVisitOrder)
}
object DfsTraversalTests:
trait BasicVisitor:
def visit(v: Vertex): Unit
class Counter extends BasicVisitor:
var count: Int = 0
def visit(v: Vertex): Unit =
count = count + 1
()
end Counter
class Tracker extends BasicVisitor:
private val output: ListBuffer[Int] = ListBuffer.empty
def snapshot(): List[Int] = output.toList
def visit(v: Vertex): Unit =
val _ = output.addOne(v.ordinal)
()
end Tracker
end DfsTraversalTests

View file

@ -0,0 +1,28 @@
package gs.graph.v0
import munit.*
class GraphDispositionTests extends FunSuite:
test("should parse and match all defined dispositions") {
val d1 = GraphDisposition.Directed
val d2 = GraphDisposition.Undirected
assertEquals(d1, GraphDisposition.Directed)
assertEquals(d2, GraphDisposition.Undirected)
assertEquals(d1.equals(d2), false)
assertEquals(d1.hashCode(), GraphDisposition.Directed.hashCode())
assertEquals(d2.hashCode(), GraphDisposition.Undirected.hashCode())
assertEquals(d1.toString(), GraphDisposition.Directed.name)
assertEquals(d2.toString(), GraphDisposition.Undirected.name)
assertEquals(d1.equals(null), false)
assertEquals(d1.equals("foo"), false)
assertEquals(d1.equals(1), false)
val p1 = GraphDisposition.parse(d1.name)
val p2 = GraphDisposition.parse(d2.name)
val p3 = GraphDisposition.parse("something else")
assertEquals(p3, None)
assertEquals(p1, Some(d1))
assertEquals(p2, Some(d2))
}

View file

@ -17,19 +17,38 @@ class VertexTests extends FunSuite:
assertEquals(v1.hashCode(), v2.hashCode())
assertNotEquals(v1.hashCode(), v3.hashCode())
assertEquals(v1 < v3, true)
assertEquals(v1 <= v3, true)
assertEquals(v1 < v2, false)
assertEquals(v1 <= v2, true)
assertEquals(v1 > v3, false)
assertEquals(v1 >= v3, false)
assertEquals(v3 > v1, true)
assertEquals(v3 >= v1, true)
assertEquals(v2 >= v1, true)
assertEquals(v2 > v1, false)
assertEquals(v1 < 3, true)
assertEquals(v1 <= 3, true)
assertEquals(v1 < 0, false)
assertEquals(v1 <= 0, true)
assertEquals(v1 > 0, false)
assertEquals(v1 >= 0, true)
assertEquals(v3 > 0, true)
assertEquals(v3 >= 0, true)
assertEquals(v1 < Size(3), true)
assertEquals(v1 <= Size(3), true)
assertEquals(v1 < Size.Zero, false)
assertEquals(v1 <= Size.Zero, true)
assertEquals(v1 > Size.Zero, false)
assertEquals(v1 >= Size.Zero, true)
assertEquals(v3 > Size.Zero, true)
assertEquals(v3 >= Size.Zero, true)
assertEquals(v1.toString(), v2.toString())
assertNotEquals(v1.toString(), v3.toString())
assertEquals(v1.compare(v3), -3)
assertEquals(v1.compare(v2), 0)
assertEquals(v1.value, 0)
assertEquals(v2.value, 0)
assertEquals(v3.value, 3)
}
test("should throw an exception if an invalid value is given") {
@ -38,3 +57,26 @@ class VertexTests extends FunSuite:
case Some(cause) =>
assertEquals(cause.getMessage(), "Vertex values must be 0 or greater.")
}
test("should instantiate a list of vertices") {
val vs = Vertex.list(0, 1, 2, 3)
assertEquals(vs, List(0, 1, 2, 3).map(Vertex(_)))
}
test("should instantiate a set of vertices") {
val vs = Vertex.set(0, 1, 2, 3, 0, 1, 2, 3)
assertEquals(vs, Set(0, 1, 2, 3).map(Vertex(_)))
}
test("should support the ordering type class") {
val o = Ordering[Vertex]
val v1 = Vertex(0)
val v2 = Vertex(0)
val v3 = Vertex(1)
val v4 = Vertex(2)
assertEquals(o.compare(v1, v2), 0)
assertEquals(o.compare(v1, v3), -1)
assertEquals(o.compare(v1, v4), -2)
assertEquals(o.compare(v4, v1), 2)
assertEquals(o.compare(v4, v3), 1)
}