From bede558347a4da4b0795ecb35b2b1897f46a4791 Mon Sep 17 00:00:00 2001 From: xenofem Date: Tue, 9 Sep 2025 20:52:09 -0400 Subject: [PATCH] Switch to a hash-based shuffle so adding new items won't change the shuffled order of existing items --- dlibrary/static/index.js | 51 ++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/dlibrary/static/index.js b/dlibrary/static/index.js index de74bb0..f8ebcdb 100644 --- a/dlibrary/static/index.js +++ b/dlibrary/static/index.js @@ -1,31 +1,36 @@ -const LCG_M = Math.pow(2, 32); -const LCG_A = 0xd9f5; -const LCG_C = 69; - -function lcg(seed) { - let value = seed % LCG_M; - - return (n) => { - value = (LCG_A * value + LCG_C) % LCG_M; - return Math.floor(n * value / LCG_M); - }; +// cyrb53a beta (c) 2023 bryc (github.com/bryc) +function cyrb53a_beta(str, seed = 0) { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (const codePoint of str) { + ch = codePoint.codePointAt(0); + h1 = Math.imul(h1 ^ ch, 0x85ebca77); + h2 = Math.imul(h2 ^ ch, 0xc2b2ae3d); + } + h1 ^= Math.imul(h1 ^ (h2 >>> 15), 0x735a2d97); + h2 ^= Math.imul(h2 ^ (h1 >>> 15), 0xcaf649a9); + h1 ^= h2 >>> 16; + h2 ^= h1 >>> 16; + return 2097152 * (h2 >>> 0) + (h1 >>> 11); } -function seedableShuffledCopy(list, seed) { - const gen = lcg(seed); - const l = [...list]; +const SEED_BOUND = Math.pow(2, 32); - for (let i = 0; i < l.length - 1; ++i) { - j = i + gen(l.length - i); - const tmp = l[i]; - l[i] = l[j]; - l[j] = tmp; - } - return l; +function seedableShuffledCopy(list, key, seed) { + const hashedIndices = list.map((elem, idx) => ({ idx, hash: cyrb53a_beta(key(elem), seed) })); + hashedIndices.sort((a, b) => { + if (a.hash > b.hash) { + return 1; + } + if (a.hash < b.hash) { + return -1; + } + return 0; + }); + return hashedIndices.map((entry) => list[entry.idx]); } function newSeed() { - return Math.floor(Math.random() * LCG_M); + return Math.floor(Math.random() * SEED_BOUND); } let receivedStore = JSON.parse(localStorage.getItem('receivedStore')) || {}; @@ -96,7 +101,7 @@ function setupIndex() { switch (ordering) { case 'shuffle': - orderedWorks = seedableShuffledCopy(WORKS, shuffleSeed); + orderedWorks = seedableShuffledCopy(WORKS, (work) => work.id, shuffleSeed); break; case 'dateAsc': orderedWorks = WORKS.toReversed();