Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,14 @@ fun SubjectTrendLineChart(
val currentPoints = pointsByIndex[indices[i]]!!
val nextPoints = pointsByIndex[indices[i+1]]!!

// Optimization: Precompute subjects to avoid O(N^3) complexity
val currentSubjects = currentPoints.map { it.third }.toSet()
val nextSubjects = nextPoints.map { it.third }.toSet()

currentPoints.forEach { p1 ->
nextPoints.forEach { p2 ->
val p1HasSuccessor = nextPoints.any { it.third == p1.third }
val p2HasPredecessor = currentPoints.any { it.third == p2.third }
val p1HasSuccessor = nextSubjects.contains(p1.third)
val p2HasPredecessor = currentSubjects.contains(p2.third)
Comment on lines +139 to +145

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the introduction of Set lookups successfully reduces the complexity from $O(N^3)$ to $O(N^2)$, we can optimize this further by hoisting independent computations out of the nested loops:

  1. p1HasSuccessor only depends on p1 and nextSubjects. It can be hoisted to the outer loop, reducing its lookups from $N \times M$ to $N$.
  2. p2HasPredecessor only depends on p2 and currentSubjects. We can precompute this for all nextPoints before entering the loops, reducing its lookups from $N \times M$ to $M$.
  3. mapTo(HashSet()) can be used instead of map { ... }.toSet() to avoid intermediate list allocations.

This reduces the total number of set lookups from $2 \times N \times M$ to just $N + M$, which is highly beneficial for drawing/rendering performance.

Suggested change
val currentSubjects = currentPoints.map { it.third }.toSet()
val nextSubjects = nextPoints.map { it.third }.toSet()
currentPoints.forEach { p1 ->
nextPoints.forEach { p2 ->
val p1HasSuccessor = nextPoints.any { it.third == p1.third }
val p2HasPredecessor = currentPoints.any { it.third == p2.third }
val p1HasSuccessor = nextSubjects.contains(p1.third)
val p2HasPredecessor = currentSubjects.contains(p2.third)
val currentSubjects = currentPoints.mapTo(HashSet()) { it.third }
val nextSubjects = nextPoints.mapTo(HashSet()) { it.third }
val p2HasPredecessorList = nextPoints.map { currentSubjects.contains(it.third) }
currentPoints.forEach { p1 ->
val p1HasSuccessor = nextSubjects.contains(p1.third)
nextPoints.forEachIndexed { p2Index, p2 ->
val p2HasPredecessor = p2HasPredecessorList[p2Index]


val isDifferentSubjectConnection = (!p1HasSuccessor || !p2HasPredecessor) && p1.third != p2.third
val isSameSubjectGap = p1.third == p2.third && (p2.first - p1.first > 1)
Expand Down
Loading