From e8597f53ba7f4f948f981d15ffb32de5010bd33f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 28 Aug 2026 04:17:59 +0000 Subject: [PATCH] Optimize deduplication in chart export for O(1) performance Replaces O(N) list membership checks with O(1) dictionary key assignments during chart payload extraction, changing O(N^2) loops to O(N). Ensures stability and insertion order relying on Python 3.7+ specs. --- .jules/bolt.md | 4 ++++ .../src/bandscope_analysis/exports/chart.py | 24 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index d54cf10fc..d861f180e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -61,3 +61,7 @@ ## 2026-07-13 - Array.from mapping optimization **Learning:** Using `Array.from({ length: N }).map(...)` creates an intermediate array of `undefined` values which requires memory allocation and garbage collection, adding O(N) unnecessary overhead in frequently re-rendered UI components. **Action:** Use `Array.from({ length: N }, (_, index) => ...)` to map elements directly during array creation, avoiding intermediate allocations. + +## 2023-08-28 - O(1) loop deduplication optimization +**Learning:** Using `item not in list` for deduplication within an O(N) loop introduces an O(N^2) complexity because list membership lookups take O(N) time. +**Action:** Use Python dictionary keys (e.g. `dict.fromkeys(items)` or `collection[item] = None` and then `list(collection.keys())`) to enforce uniqueness, which brings the lookup time complexity to O(1) and overall processing to O(N), while maintaining insertion order natively since Python 3.7. diff --git a/services/analysis-engine/src/bandscope_analysis/exports/chart.py b/services/analysis-engine/src/bandscope_analysis/exports/chart.py index 3a84b59c8..a109c89db 100644 --- a/services/analysis-engine/src/bandscope_analysis/exports/chart.py +++ b/services/analysis-engine/src/bandscope_analysis/exports/chart.py @@ -78,14 +78,14 @@ def _active_role_ids(section: Mapping[str, object]) -> list[str] | None: part_graph = section.get("partGraph") if not isinstance(part_graph, list): return None - active: list[str] = [] + active: dict[str, None] = {} for node in part_graph: if not isinstance(node, Mapping) or node.get("is_active") is not True: continue role_id = node.get("role_id") if isinstance(role_id, str) and role_id and role_id not in active: - active.append(role_id) - return active + active[role_id] = None + return list(active.keys()) def _active_roles(section: Mapping[str, object]) -> list[Mapping[str, object]]: @@ -121,25 +121,25 @@ def _role_display_name(role: Mapping[str, object]) -> str | None: def _active_role_names(section: Mapping[str, object]) -> list[str]: """Return de-duplicated display names for the section's active roles.""" - names: list[str] = [] + names: dict[str, None] = {} for role in _active_roles(section): name = _role_display_name(role) if name is not None and name not in names: - names.append(name) - return names + names[name] = None + return list(names.keys()) def _section_cue(section: Mapping[str, object]) -> str: """Join the active roles' cue values into a single cue string.""" - cues: list[str] = [] + cues: dict[str, None] = {} for role in _active_roles(section): cue = role.get("cue") if not isinstance(cue, Mapping): continue value = cue.get("value") if isinstance(value, str) and value and value not in cues: - cues.append(value) - return "; ".join(cues) + cues[value] = None + return "; ".join(cues.keys()) def _confidence_level(section: Mapping[str, object]) -> str | None: @@ -188,7 +188,7 @@ def _section_lines(sections: list[Mapping[str, object]]) -> list[str]: def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object]]) -> list[str]: """Build the footer: per-role rehearsal priorities and the export focus.""" lines: list[str] = [] - priorities: list[str] = [] + priorities: dict[str, None] = {} for section in sections: for role in _section_roles(section): name = _role_display_name(role) @@ -197,10 +197,10 @@ def _footer_lines(song: Mapping[str, object], sections: list[Mapping[str, object continue entry = f" - {name}: {priority}" if entry not in priorities: - priorities.append(entry) + priorities[entry] = None if priorities: lines.append("Priorities:") - lines.extend(priorities) + lines.extend(priorities.keys()) summary = song.get("exportSummary") if isinstance(summary, Mapping): headline = summary.get("headline")