diff --git a/.java-version b/.java-version new file mode 100644 index 0000000..422a10d --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +openjdk64-16 diff --git a/build.gradle.kts b/build.gradle.kts index dd694c3..53e6b4e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,12 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + group = "com.github.cs125-illinois" -version = "2021.8.1" +version = "2021.10.0" plugins { - kotlin("jvm") version "1.5.30" + kotlin("jvm") version "1.5.31" `maven-publish` - id("org.jmailen.kotlinter") version "3.5.1" + id("org.jmailen.kotlinter") version "3.6.0" id("com.github.ben-manes.versions") version "0.39.0" id("io.gitlab.arturbosch.detekt") version "1.18.1" } @@ -12,6 +14,10 @@ repositories { mavenCentral() maven("https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") } +dependencies { + testImplementation("io.kotest:kotest-runner-junit5:4.6.3") + testImplementation("org.slf4j:slf4j-simple:1.7.32") +} tasks.dependencyUpdates { fun String.isNonStable() = !( listOf("RELEASE", "FINAL", "GA").any { toUpperCase().contains(it) } @@ -30,3 +36,13 @@ publishing { detekt { buildUponDefaultConfig = true } +tasks.withType { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_16.toString() + } +} +tasks.withType { + useJUnitPlatform() + enableAssertions = true + jvmArgs("-ea", "-Xmx1G", "-Xss256k", "-Dfile.encoding=UTF-8") +} diff --git a/src/main/java/cs125/graphs/UndirectedGraph.kt b/src/main/java/cs125/graphs/UndirectedGraph.kt new file mode 100644 index 0000000..da06760 --- /dev/null +++ b/src/main/java/cs125/graphs/UndirectedGraph.kt @@ -0,0 +1,185 @@ +package cs125.graphs + +class Node(val value: T, private val index: Int, val neighbors: MutableSet> = mutableSetOf()) { + override fun toString() = "Node $index ($value)" +} + +class InputNode(var value: T) + +private fun find(node: Node, visited: MutableSet>) { + visited += node + for (neighbor in node.neighbors) { + if (neighbor !in visited) { + find(neighbor, visited) + } + } +} + +private fun find(node: Node): Set> { + val nodes = mutableSetOf>() + find(node, nodes) + return nodes +} + +fun Map, Set>>.toGraph(): Node { + check(isNotEmpty()) { "Graph is empty" } + val mapping = keys.mapIndexed { index, key -> key to Node(key.value, index) }.toMap() + forEach { (key, values) -> + check(mapping[key] != null) { "Missing mapping for key in graph creation" } + mapping[key]!!.neighbors.addAll(values.map { + mapping[it] ?: error("Missing mapping for value in graph creation") + }) + } + + mapping.values.forEach { + check(it !in it.neighbors) { "Graph contains a self-edge" } + check(find(it) == mapping.values.toSet()) { "Graph is not connected" } + for (node in it.neighbors) { + check(it in node.neighbors) { "Graph is not undirected" } + } + } + return mapping.values.first() +} + +fun singleNodeGraph(value: T) = mapOf(InputNode(value) to setOf>()).toGraph() + +fun twoNodeGraph(first: T, second: T): Node { + val mapping = mapOf(first to InputNode(first), second to InputNode(second)) + return mapOf( + mapping[first]!! to setOf(mapping[second]!!), + mapping[second]!! to setOf(mapping[first]!!) + ).toGraph() +} + +fun circleGraph(list: List): Node { + require(list.size >= 2) { "List has fewer than two elements" } + val mapping = list.associateWith { + InputNode(it) + } + val edges = mapping.values.associateWith { mutableSetOf>() } + for (i in 0 until (list.size - 1)) { + edges[mapping[list[i]]]!! += mapping[list[i + 1]]!! + edges[mapping[list[i + 1]]]!! += mapping[list[i]]!! + } + edges[mapping[list[0]]]!! += mapping[list[list.size - 1]]!! + edges[mapping[list[list.size - 1]]]!! += mapping[list[0]]!! + return edges.toGraph() +} + +fun fullyConnectedGraph(list: List): Node { + require(list.size >= 2) { "List has fewer than two elements" } + val mapping = list.associateWith { + InputNode(it) + } + val edges = mapping.values.associateWith { mutableSetOf>() } + for (i in list.indices) { + for (j in list.indices - i) { + edges[mapping[list[i]]]!! += mapping[list[j]]!! + edges[mapping[list[j]]]!! += mapping[list[i]]!! + } + } + return edges.toGraph() +} + +/* +class UndirectedGraph(inputEdges: Map, Set>>) { + private val edges: Map> + val node: Node + + init { + check(inputEdges.isNotEmpty()) { "Graph is empty" } + val edgeMapping = inputEdges.keys.mapIndexed { index, key -> + key to Node(key, index) + }.toMap() + edges = inputEdges.map { (key, values) -> + check(edgeMapping[key] != null) { "Missing mapping for key in graph creation" } + edgeMapping[key]!! to values.map { value -> + edgeMapping[value] ?: error("Missing mapping for value during graph creation") + }.toSet() + }.toMap() + + for ((first, neighbors) in edges) { + check(first !in neighbors) { "Graph contains a self-edge" } + for (second in neighbors) { + check(second.connectedTo(first)) { "Graph is not undirected" } + } + check(first.find() == edges.keys) { "Graph is not connected" } + } + + node = edges.keys.first() + } + + inner class Node(var value: T, private val index: Int) { + constructor(graphNode: InputNode, index: Int) : this(graphNode.value, index) + + val neighbors: Set + get() = edges[this]!! + + internal fun connectedTo(second: Node) = edges[this]?.contains(second) ?: false + internal fun find(): Set { + val nodes = mutableSetOf() + find(nodes) + return nodes + } + + private fun find(visited: MutableSet) { + visited += this + for (neighbor in neighbors) { + if (neighbor !in visited) { + neighbor.find(visited) + } + } + } + + override fun toString() = "Node $index ($value)" + } + + companion object { + @JvmStatic + fun singleNodeGraph(value: T) = UndirectedGraph(mapOf(InputNode(value) to setOf())) + + @JvmStatic + fun twoNodeGraph(first: T, second: T): UndirectedGraph { + val mapping = mapOf(first to InputNode(first), second to InputNode(second)) + return UndirectedGraph( + mapOf( + mapping[first]!! to setOf(mapping[second]!!), + mapping[second]!! to setOf(mapping[first]!!) + ) + ) + } + + @JvmStatic + fun circleGraph(list: List): UndirectedGraph { + require(list.size >= 2) { "List has fewer than two elements" } + val mapping = list.associateWith { + InputNode(it) + } + val edges = mapping.values.associateWith { mutableSetOf>() } + for (i in 0 until (list.size - 1)) { + edges[mapping[list[i]]]!! += mapping[list[i + 1]]!! + edges[mapping[list[i + 1]]]!! += mapping[list[i]]!! + } + edges[mapping[list[0]]]!! += mapping[list[list.size - 1]]!! + edges[mapping[list[list.size - 1]]]!! += mapping[list[0]]!! + return UndirectedGraph(edges) + } + + @JvmStatic + fun fullyConnectedGraph(list: List): UndirectedGraph { + require(list.size >= 2) { "List has fewer than two elements" } + val mapping = list.associateWith { + InputNode(it) + } + val edges = mapping.values.associateWith { mutableSetOf>() } + for (i in list.indices) { + for (j in list.indices - i) { + edges[mapping[list[i]]]!! += mapping[list[j]]!! + edges[mapping[list[j]]]!! += mapping[list[i]]!! + } + } + return UndirectedGraph(edges) + } + } +} +*/ \ No newline at end of file diff --git a/src/test/java/com/example/Example.java b/src/test/java/com/example/Example.java new file mode 100644 index 0000000..646a7fe --- /dev/null +++ b/src/test/java/com/example/Example.java @@ -0,0 +1,33 @@ +package com.example; + +import cs125.graphs.Node; + +import java.util.HashSet; +import java.util.Set; + +public class Example { + public static int size(Node graph) { + Set> visited = new HashSet<>(); + traverse(graph, visited); + return visited.size(); + } + + private static void traverse(Node node, Set> visited) { + visited.add(node); + for (Node neighbor : node.getNeighbors()) { + if (!(visited.contains(neighbor))) { + traverse(neighbor, visited); + } + } + } + + public static int sum(Node graph) { + Set> visited = new HashSet<>(); + traverse(graph, visited); + int sum = 0; + for (Node node : visited) { + sum += node.getValue(); + } + return sum; + } +} diff --git a/src/test/kotlin/TestUndirectedGraph.kt b/src/test/kotlin/TestUndirectedGraph.kt new file mode 100644 index 0000000..eb9b3bf --- /dev/null +++ b/src/test/kotlin/TestUndirectedGraph.kt @@ -0,0 +1,34 @@ +import com.example.Example +import cs125.graphs.circleGraph +import cs125.graphs.fullyConnectedGraph +import cs125.graphs.singleNodeGraph +import cs125.graphs.twoNodeGraph +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class TestUndirectedGraph : StringSpec({ + "it should create a single-node graph" { + singleNodeGraph(8).also { + Example.size(it) shouldBe 1 + Example.sum(it) shouldBe 8 + } + } + "it should create a two-node graph" { + twoNodeGraph(8, 16).also { + Example.size(it) shouldBe 2 + Example.sum(it) shouldBe 24 + } + } + "it should create a circular graph" { + circleGraph((0..31).toList()).also { + Example.size(it) shouldBe 32 + Example.sum(it) shouldBe (0..31).sum() + } + } + "it should create a fully-connected graph" { + fullyConnectedGraph((32..63).toList()).also { + Example.size(it) shouldBe 32 + Example.sum(it) shouldBe (32..63).sum() + } + } +}) \ No newline at end of file