Repository: jraska/modules-graph-assert
Pull request #265 (2024-07-23 15:35:34)

Calculating subgraph to O(V+E) complexity
- Previous complexity O(V<sup>V</sup>)
- Current complexity O(V+E)
- _One would say this is too obvious... to easy to forget._ 🙈 


Patch:
diff --git a/plugin/src/main/kotlin/com/jraska/module/graph/DependencyGraph.kt b/plugin/src/main/kotlin/com/jraska/module/graph/DependencyGraph.kt
index 5edd981..2426a81 100644
--- a/plugin/src/main/kotlin/com/jraska/module/graph/DependencyGraph.kt
+++ b/plugin/src/main/kotlin/com/jraska/module/graph/DependencyGraph.kt
@@ -62,7 +62,7 @@ class DependencyGraph private constructor() {
     require(nodes.contains(key)) { "Dependency Tree doesn't contain module: $key" }
 
     val connections = mutableListOf<Pair<String, String>>()
-    addConnections(nodes.getValue(key), connections, mutableSetOf())
+    addConnections(nodes.getValue(key), connections, mutableSetOf(), mutableSetOf())
 
     return if (connections.isEmpty()) {
       createSingular(key)
@@ -79,9 +79,17 @@ class DependencyGraph private constructor() {
   }
 
   private fun addConnections(
-    node: Node, into: MutableList<Pair<String, String>>,
-    path: MutableSet<Node>
+    node: Node,
+    into: MutableList<Pair<String, String>>,
+    path: MutableSet<Node>,
+    visited: MutableSet<Node>,
   ) {
+    if (visited.contains(node)) {
+      return
+    } else {
+      visited.add(node)
+    }
+
     path.add(node)
     node.dependsOn.forEach { dependant ->
       into.add(node.key to dependant.key)
@@ -91,7 +99,7 @@ class DependencyGraph private constructor() {
         val pathText = path.joinToString(separator = ", ") { it.key }
         throw IllegalStateException("Dependency cycle detected! Cycle in nodes: '${pathText}'.")
       }
-      addConnections(dependant, into, path)
+      addConnections(dependant, into, path, visited)
     }
 
     path.remove(node)


Test patch:
diff --git a/plugin/src/test/kotlin/com/jraska/module/graph/DependencyGraphPerformanceTest.kt b/plugin/src/test/kotlin/com/jraska/module/graph/DependencyGraphPerformanceTest.kt
index 048a3d2..d90c71a 100644
--- a/plugin/src/test/kotlin/com/jraska/module/graph/DependencyGraphPerformanceTest.kt
+++ b/plugin/src/test/kotlin/com/jraska/module/graph/DependencyGraphPerformanceTest.kt
@@ -29,9 +29,27 @@ class DependencyGraphPerformanceTest {
     assert(statistics.longestPath.pathString().startsWith("23 -> 31 -> 36 -> 57 -> 61 -> 72 -> 74 -> 75"))
   }
 
-  @Test(timeout = 10000)
-  fun whenTheGraphIsLarge_statisticsSubGraphCreatedFast() {
+  @Test(timeout = 1_000)
+  fun whenTheGraphIsLarge_statisticsOfSubgraphMatchFast() {
+    val subGraphStatistics = dependencyGraph.subTree("31").statistics()
+
+    assert(subGraphStatistics.height == 58)
+    assert(subGraphStatistics.longestPath.pathString().startsWith("31 -> 36 -> 57 -> 61 -> 72 -> 74 -> 75"))
+  }
+
+  @Test(timeout = 1_000)
+  fun whenTheGraphIsLarge_statisticsCreatedFast() {
     val subGraphStatistics = dependencyGraph.subTree("500").statistics()
     assert(subGraphStatistics.modulesCount == 281)
   }
+
+  @Test(timeout = 1_000) // was running out of heap before optimisation
+  fun whenTheGraphIsLarge_statisticsLargeCreatedFast() {
+    val subGraphStatistics = dependencyGraph.subTree("2").statistics()
+
+    assert(subGraphStatistics.modulesCount == 870)
+    assert(subGraphStatistics.edgesCount == 11650)
+    assert(subGraphStatistics.height == 55)
+    assert(subGraphStatistics.longestPath.pathString().startsWith("2 -> 30 -> 76 -> 105 -> 119 -> "))
+  }
 }