From 05f18aa1bca45cfcdff6a33dc6dd8578913e54a0 Mon Sep 17 00:00:00 2001 From: LeanBitLab <245915690+LeanBitLab@users.noreply.github.com> Date: Tue, 6 Oct 2026 08:12:33 +0000 Subject: [PATCH] Refactor data parsing deduplication to use HashSet --- .jules/bolt.md | 4 ++++ .../com/example/reddittube/data/DataRepository.kt | 13 ++++++++----- .../reddittube/ui/main/components/MinimalButton.kt | 1 - 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 8f342ab..bdd617d 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,3 +3,7 @@ ## 2024-05-24 - O(N) Iteration in Cache Size Calculation **Learning:** Calculating dynamic capacity dynamically via `store.values.sumOf { it.sizeBytes }` effectively incurs an O(N) cost on every `put` because it runs when `trimToCapacity()` calls `dynamicCapacity`. When cache sizes get large, this causes O(N²) overall insertion performance and UI stutters. **Action:** Use an `AtomicLong` (O(1)) to track the running sum of cache size instead of dynamically iterating through the entire collection. + +## 2024-05-25 - O(N^2) loop optimization +**Learning:** Checking for item existence using `list.none { it.id == post.id }` within loops processing remote data causes an O(N^2) operation, harming parse performance for large JSON blocks. +**Action:** Use an auxiliary `HashSet` to trace seen IDs (`seenIds.add(post.id)`) which reduces time complexity of deduplication check from O(N) to O(1) resulting in overall O(N) operation for loop. diff --git a/app/src/main/java/com/example/reddittube/data/DataRepository.kt b/app/src/main/java/com/example/reddittube/data/DataRepository.kt index 90c2f6a..0a44f14 100644 --- a/app/src/main/java/com/example/reddittube/data/DataRepository.kt +++ b/app/src/main/java/com/example/reddittube/data/DataRepository.kt @@ -259,6 +259,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { val token = RedditOAuthHelper.getOrFetchAccessToken(context) if (token == null) { emit(SearchVideosResult(emptyList(), null)); return@flow } val results = mutableListOf() + val seenIds = mutableSetOf() var currentAfter = after var finalAfter: String? = null try { @@ -276,7 +277,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { for (i in 0 until children.length()) { val childData = children.getJSONObject(i).optJSONObject("data") ?: continue parseRedditPost(childData)?.let { post -> - if (results.none { it.id == post.id }) { + if (seenIds.add(post.id)) { results.add(post) } } @@ -312,6 +313,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { subs.map { sub -> async(Dispatchers.IO) { val subPosts = mutableListOf() + val seenIds = mutableSetOf() var after = afterMap[sub] ?: "" for (page in 0 until 5) { if (subPosts.size >= 15) break @@ -327,7 +329,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { for (i in 0 until children.length()) { val childData = children.getJSONObject(i).optJSONObject("data") ?: continue parseRedditPost(childData)?.let { post -> - if (subPosts.none { it.id == post.id }) { + if (seenIds.add(post.id)) { subPosts.add(post) } } @@ -508,6 +510,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { private suspend fun performOAuthRequest(subreddit: String, token: String, sort: String = "hot", feed: String = "explore"): List { val list = mutableListOf() + val seenIds = mutableSetOf() var after: String? = null val sortPath = if (sort.contains("?")) sort.substringBefore("?") else sort val sortExtra = if (sort.contains("?")) "&" + sort.substringAfter("?") else "" @@ -527,7 +530,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { for (i in 0 until children.length()) { val childData = children.getJSONObject(i).optJSONObject("data") ?: continue parseRedditPost(childData)?.let { post -> - if (list.none { it.id == post.id }) { + if (seenIds.add(post.id)) { list.add(post) } } @@ -553,7 +556,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { for (i in 0 until children.length()) { val childData = children.getJSONObject(i).optJSONObject("data") ?: continue parseRedditPost(childData)?.let { post -> - if (list.none { it.id == post.id }) { + if (seenIds.add(post.id)) { list.add(post) } } @@ -576,7 +579,7 @@ class DefaultDataRepository(private val context: Context) : DataRepository { for (i in 0 until children.length()) { val childData = children.getJSONObject(i).optJSONObject("data") ?: continue val post = parseRedditPost(childData) - if (post != null && list.none { it.id == post.id }) { + if (post != null && seenIds.add(post.id)) { list.add(post) } } diff --git a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt index 056265a..8879150 100644 --- a/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt +++ b/app/src/main/java/com/example/reddittube/ui/main/components/MinimalButton.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp