From 5a3a08b6cc35895abaf3e0984134d5ff5c0a16b8 Mon Sep 17 00:00:00 2001 From: anyulled <100741+anyulled@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:50:18 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Optimize=20random=20array?= =?UTF-8?q?=20shuffling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the O(N log N) `map-sort-map` pattern with an O(N) Fisher-Yates algorithm in `getRandomSpeakers` and `shuffleArray`. This avoids intermediate object allocations and reduces Garbage Collection overhead. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- .jules/bolt.md | 5 +++++ hooks/useSpeakers.ts | 14 ++++++++++---- hooks/useTalks.ts | 15 +++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index d81fd70a..5438b6b7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -17,3 +17,8 @@ **Learning:** When attempting to optimize an O(N^2) array spread operation (`[...existing, talk]`) inside a grouping loop in `groupTalksByTrack`, the purely functional/immutable constraint specified by the team (and the lack of `Map.groupBy` support in Node 20.x Jest environments) means that we must fall back to immutable reductions. **Action:** When constraints require strict immutability without mutation of objects, use `reduce` with object and array spreads (e.g., `{ ...acc, [key]: [...(acc[key] || []), item] }`) even if it introduces O(N^2) overhead for large arrays. Avoid using `push()` or modifying accumulators directly. Always run Prettier/formatting checks before merge to resolve CI failures. + +## 2026-03-24 - Fisher-Yates shuffle + +**Learning:** Replaced the O(N log N) `map-sort-map` pattern used for array shuffling with an O(N) Fisher-Yates algorithm. This in-place approach on a shallow copy avoids intermediate object allocations and reduces GC overhead significantly. +**Action:** Use Fisher-Yates array shuffling in-place over O(N log N) solutions to prevent Garbage Collection issues. diff --git a/hooks/useSpeakers.ts b/hooks/useSpeakers.ts index 94f5b0a7..37dbd4e3 100644 --- a/hooks/useSpeakers.ts +++ b/hooks/useSpeakers.ts @@ -44,10 +44,16 @@ export function getRandomSpeakers(speakers: Speaker[], count: number): Speaker[] if (!speakers || speakers.length === 0) return []; if (speakers.length <= count) return speakers; - const shuffled = [...speakers] - .map((speaker) => ({ speaker, sortKey: Math.random() })) - .sort((a, b) => a.sortKey - b.sortKey) - .map((item) => item.speaker); + const shuffled = [...speakers]; + shuffled.forEach((_, i) => { + const j = Math.floor(Math.random() * (i + 1)); + // eslint-disable-next-line security/detect-object-injection + const temp = shuffled[i]; + // eslint-disable-next-line security/detect-object-injection + shuffled[i] = shuffled[j]; + // eslint-disable-next-line security/detect-object-injection + shuffled[j] = temp; + }); return shuffled.slice(0, count); } diff --git a/hooks/useTalks.ts b/hooks/useTalks.ts index 2dd03ebd..afa10819 100644 --- a/hooks/useTalks.ts +++ b/hooks/useTalks.ts @@ -139,10 +139,17 @@ export const getTalkSpeakersWithDetails = async (year: string | number, speakerI * Shuffle an array using Fisher-Yates algorithm */ const shuffleArray = (array: T[]): T[] => { - return [...array] - .map((item) => ({ item, sortKey: Math.random() })) - .sort((a, b) => a.sortKey - b.sortKey) - .map((entry) => entry.item); + const result = [...array]; + result.forEach((_, i) => { + const j = Math.floor(Math.random() * (i + 1)); + // eslint-disable-next-line security/detect-object-injection + const temp = result[i]; + // eslint-disable-next-line security/detect-object-injection + result[i] = result[j]; + // eslint-disable-next-line security/detect-object-injection + result[j] = temp; + }); + return result; }; /**