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 7011cd5..acd3e3c 100644 --- a/modules/core/src/main/scala/gs/graph/v0/Graph.scala +++ b/modules/core/src/main/scala/gs/graph/v0/Graph.scala @@ -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. */ diff --git a/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala b/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala index 0e9d0eb..9041e1f 100644 --- a/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala +++ b/modules/core/src/main/scala/gs/graph/v0/GraphDisposition.scala @@ -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 diff --git a/modules/core/src/main/scala/gs/graph/v0/GraphTraversal.scala b/modules/core/src/main/scala/gs/graph/v0/GraphTraversal.scala index f26f01c..56f707c 100644 --- a/modules/core/src/main/scala/gs/graph/v0/GraphTraversal.scala +++ b/modules/core/src/main/scala/gs/graph/v0/GraphTraversal.scala @@ -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 diff --git a/modules/core/src/test/scala/gs/graph/v0/BfsTraversalTests.scala b/modules/core/src/test/scala/gs/graph/v0/BfsTraversalTests.scala new file mode 100644 index 0000000..28b3d78 --- /dev/null +++ b/modules/core/src/test/scala/gs/graph/v0/BfsTraversalTests.scala @@ -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 diff --git a/modules/core/src/test/scala/gs/graph/v0/DfsTraversalTests.scala b/modules/core/src/test/scala/gs/graph/v0/DfsTraversalTests.scala new file mode 100644 index 0000000..53657ef --- /dev/null +++ b/modules/core/src/test/scala/gs/graph/v0/DfsTraversalTests.scala @@ -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 diff --git a/modules/core/src/test/scala/gs/graph/v0/GraphDispositionTests.scala b/modules/core/src/test/scala/gs/graph/v0/GraphDispositionTests.scala new file mode 100644 index 0000000..a5395ee --- /dev/null +++ b/modules/core/src/test/scala/gs/graph/v0/GraphDispositionTests.scala @@ -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)) + } diff --git a/modules/core/src/test/scala/gs/graph/v0/VertexTests.scala b/modules/core/src/test/scala/gs/graph/v0/VertexTests.scala index 305d2d0..2603486 100644 --- a/modules/core/src/test/scala/gs/graph/v0/VertexTests.scala +++ b/modules/core/src/test/scala/gs/graph/v0/VertexTests.scala @@ -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) + }