Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
## 2024-05-24 - Loop Allocation Hot Paths
**Learning:** Rendering directory entries with repeated string concatenation and list-based exclusion lookups creates avoidable allocation and lookup cost in large directories.
**Action:** Use `StringBuilder` for entry rendering and a `Set` for excluded file names.
## 2024-05-19 - Replace custom LinkedList with ArrayDeque
**Learning:** The custom `LinkedList` implementation in `src/main/kotlin/html4tree/util.kt` had an inefficient O(N) push operation and potential state bugs.
**Action:** Replaced it with the standard `java.util.ArrayDeque` for O(1) push operations, significantly improving performance for deep directory structures, while preserving the exact same tree traversal order and logic.
13 changes: 7 additions & 6 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ fun main(args: Array<String>) = Html4tree().main(args)

fun go(topDir: String, maxLevel: Int) {
require(topDir.isNotBlank())
val top_dir = File(topDir).canonicalFile
val top_dir = File(topDir).absoluteFile.toPath().normalize().toFile()
require(Files.isDirectory(top_dir.toPath(), LinkOption.NOFOLLOW_LINKS)) { "Top directory must be an existing non-symlink directory" }

val ll = LinkedList()
// ⚡ Bolt: Use ArrayDeque instead of inefficient custom LinkedList (O(1) vs O(N) push)
val ll = java.util.ArrayDeque<LinkedListEntry>()

ll.push(LinkedListEntry(top_dir,0))
ll.addLast(LinkedListEntry(top_dir,0))

var lle: LinkedListEntry? = ll.pull()
var lle: LinkedListEntry? = ll.pollFirst()

while(lle != null && Files.isDirectory(lle.file.toPath(), LinkOption.NOFOLLOW_LINKS)){
val currentLevel: Int = lle.level
Expand All @@ -40,11 +41,11 @@ fun go(topDir: String, maxLevel: Int) {
if(maxLevel == -1 || currentLevel < maxLevel) {
lle.file.listFiles()?.forEach {
if(Files.isDirectory(it.toPath(), LinkOption.NOFOLLOW_LINKS)){
ll.push( LinkedListEntry(it, currentLevel+1))
ll.addLast(LinkedListEntry(it, currentLevel+1))
}
}
}
lle = ll.pull()
lle = ll.pollFirst()
}
}

Expand Down
42 changes: 0 additions & 42 deletions src/main/kotlin/html4tree/util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,4 @@ package html4tree

import java.io.File

data class Entry (val data: File, val level: Int, var next: Entry?)

data class LinkedListEntry(val file: File, val level: Int)

class LinkedList {
var first: Entry? = null
var last: Entry? = null

fun push(lle: LinkedListEntry) {
if(last == null){
last = Entry(lle.file, lle.level, null)
first = last
} else {
val nextEntry = Entry(lle.file, lle.level, null)
val currentFirst = first
if (currentFirst == null) {
var currentLast = last!!
while (currentLast.next != null) {
currentLast = currentLast.next!!
}
currentLast.next = nextEntry
} else {
currentFirst.next = nextEntry
}
first = nextEntry
}
}

fun pull(): LinkedListEntry? {
val l: Entry? = last
if(l != null) {
last = l.next
}

if(l == null){
return null
} else {
l.next = null
return LinkedListEntry(l.data, l.level)
}
}

}
79 changes: 0 additions & 79 deletions src/test/kotlin/html4tree/UtilTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,9 @@ package html4tree
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertNull

class UtilTest {

@Test
fun testLinkedList() {
val list = LinkedList()
assertNull(list.pull())

val file1 = File("file1")
val file2 = File("file2")
val file3 = File("file3")

list.push(LinkedListEntry(file1, 0))
list.push(LinkedListEntry(file2, 1))
list.push(LinkedListEntry(file3, 2))

var pulled = list.pull()
assertEquals(file1, pulled?.file)
assertEquals(0, pulled?.level)

pulled = list.pull()
assertEquals(file2, pulled?.file)
assertEquals(1, pulled?.level)

pulled = list.pull()
assertEquals(file3, pulled?.file)
assertEquals(2, pulled?.level)
assertNull(list.pull())
}

@Test
fun testEntryDataClass() {
val file1 = File("file1")
val entry1 = Entry(file1, 0, null)
val entry2 = Entry(file1, 0, null)

assertEquals(entry1, entry2)
assertEquals("Entry(data=file1, level=0, next=null)", entry1.toString())
}

@Test
fun testLinkedListEntryDataClass() {
val file1 = File("file1")
Expand All @@ -54,45 +16,4 @@ class UtilTest {
assertEquals("LinkedListEntry(file=file1, level=0)", entry1.toString())
}

@Test
fun testLinkedListPushExisting() {
val list = LinkedList()
list.push(LinkedListEntry(File("f1"), 0))
list.push(LinkedListEntry(File("f2"), 0))
val entry1 = list.pull()
val entry2 = list.pull()
assertEquals(File("f1"), entry1?.file)
assertEquals(File("f2"), entry2?.file)
}

@Test
fun testLinkedListAccessors() {
val list = LinkedList()
list.first = Entry(File("test"), 0, null)
list.last = Entry(File("test"), 0, null)
assertEquals(File("test"), list.first?.data)
assertEquals(File("test"), list.last?.data)
}

@Test
fun testLinkedListPushNullFirst() {
val list = LinkedList()
list.last = Entry(File("fake"), 0, null)
list.push(LinkedListEntry(File("f3"), 0))
assertEquals(File("fake"), list.pull()?.file)
assertEquals(File("f3"), list.pull()?.file)
assertEquals(File("f3"), list.first?.data)
}

@Test
fun testLinkedListPushNullFirstWithExistingChain() {
val list = LinkedList()
list.last = Entry(File("f1"), 0, Entry(File("f2"), 0, null))
list.push(LinkedListEntry(File("f3"), 0))

assertEquals(File("f1"), list.pull()?.file)
assertEquals(File("f2"), list.pull()?.file)
assertEquals(File("f3"), list.pull()?.file)
assertNull(list.pull())
}
}
Loading