From ee3c8b4be8773a98766d40428e01d0569609a30f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 28 Jun 2026 03:44:34 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94]=20O(n^2)=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8=20=EA=B2=80=EC=83=89=20=EB=B0=8F=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EC=97=B0=EA=B2=B0=EC=9D=84=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=95=98=EC=97=AC=20=EC=86=8D=EB=8F=84=20=ED=96=A5?= =?UTF-8?q?=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `process_ignore_file`가 List 대신 Set을 반환하도록 변경하여 O(n) 조회에서 O(1)로 단축 - `index_middle` 루프에서 문자열 연결 (`+=`) 대신 `StringBuilder`를 사용하여 메모리 할당 병목 제거 - 불필요한 `.sorted()` 호출을 제거 - 100% 테스트 커버리지 및 Jacoco 설정 추가 --- .jules/bolt.md | 4 + build.gradle | 25 ++- src/main/kotlin/html4tree/main.kt | 21 +-- src/test/kotlin/html4tree/LinkedListTest.kt | 104 +++++++++++++ src/test/kotlin/html4tree/MainTest.kt | 163 ++++++++++++++++++++ 5 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 src/test/kotlin/html4tree/LinkedListTest.kt create mode 100644 src/test/kotlin/html4tree/MainTest.kt diff --git a/.jules/bolt.md b/.jules/bolt.md index 39f32f6e..1f3362b9 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,7 @@ ## 2024-06-21 - Regex Compilation in Loops **Learning:** In Kotlin, compiling regular expressions (`.toRegex()`) inside a loop over files is a significant O(N * M) performance bottleneck when processing ignore files (N files * M rules). **Action:** Always map string rules to compiled `Regex` objects outside of the file iteration loop (O(M) compilation) to avoid unnecessary regex re-compilations. + +## 2024-06-28 - Avoid O(n^2) operations when processing lists +**Learning:** Checking for containment (`in` / `!in`) within a `List` structure scales as O(n). When executed repeatedly inside a loop covering all `n` files of a directory listing, it inherently creates an O(n^2) operation, acting as a performance pitfall as directory sizes grow. Additionally, repeatedly concatenating strings (`+=`) in such loops leads to O(n^2) memory reallocation operations. +**Action:** When performing `n` membership queries against an exclusion list or tracking items, convert the collection to a `Set` for O(1) lookups. In Kotlin, use a `StringBuilder` or `.joinToString` rather than concatenating with `+=` within iterative structures to optimize performance. diff --git a/build.gradle b/build.gradle index 8e088074..73848e27 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { apply plugin: 'kotlin' apply plugin: 'application' +apply plugin: 'jacoco' mainClassName = 'html4tree.MainKt' @@ -33,4 +34,26 @@ jar { } from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } -} \ No newline at end of file +} + +jacoco { + toolVersion = "0.8.7" +} + +jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + html.destination file("${buildDir}/jacocoHtml") + } +} + +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 1.0 + } + } + } +} diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 59999fce..a8165cd3 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -54,7 +54,7 @@ fun String.urlEncodePath(): String { return java.net.URLEncoder.encode(this, "UTF-8").replace("+", "%20") } -fun process_ignore_file(curr_dir: File): List { +fun process_ignore_file(curr_dir: File): Set { val ignore_filename = ".html4ignore" @@ -62,14 +62,15 @@ fun process_ignore_file(curr_dir: File): List { val ignore_file = File(ignore_file_path) - val files_to_exclude = mutableListOf() + // BOLT OPTIMIZATION: Use mutableSetOf instead of mutableListOf for O(1) containment checks below + val files_to_exclude = mutableSetOf() if(ignore_file.exists()){ val ignored_regexes = mutableListOf() ignore_file.forEachLine { ignored_regexes.add(("^"+it+"$").toRegex()) } - curr_dir.list().sorted().forEach { + curr_dir.list().forEach { val current = it ignored_regexes.forEach { regex -> if(regex.matches(current)){ @@ -79,16 +80,14 @@ fun process_ignore_file(curr_dir: File): List { } } - if ("index.html" !in files_to_exclude) - files_to_exclude.add("index.html") - + files_to_exclude.add("index.html") return files_to_exclude } fun process_dir(curr_dir: File){ - val exclude: List = process_ignore_file(curr_dir) + val exclude: Set = process_ignore_file(curr_dir) val css = """