From dfde7cb571074a92c9d7f1b8667d7d43de0e3fa4 Mon Sep 17 00:00:00 2001 From: LeanBitLab <245915690+LeanBitLab@users.noreply.github.com> Date: Tue, 6 Oct 2026 08:09:55 +0000 Subject: [PATCH] Optimize O(N^2) list dedup to O(1) using Set Replaced `list.none { it.id == post.id }` with a Set to track seen IDs in `DataRepository.kt`. This improves the parsing speed from O(N^2) to O(1) expected time, particularly relevant when loading many fallback subreddits. --- .jules/bolt.md | 3 +++ .../com/example/reddittube/data/DataRepository.kt | 13 ++++++++----- .../reddittube/ui/main/components/MinimalButton.kt | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 8f342ab..06b09d3 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,3 +3,6 @@ ## 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. +## 2026-10-06 - O(N^2) Loop Optimization via Set +**Learning:** Checking for duplicates using `list.none { it.id == post.id }` inside a loop results in O(N^2) time complexity. Using a `Set` and `set.add()` reduces this to O(1) per iteration, vastly improving performance for large lists. +**Action:** Use a `Set` to track already added items when removing duplicates from a list inside a 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..24a2914 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 subSeenIds = 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 (subSeenIds.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