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; }; /**