Created
August 7, 2020 13:24
-
-
Save grevych/e3edca3d1c68de4617fa40a17420d156 to your computer and use it in GitHub Desktop.
Revisions
-
grevych created this gist
Aug 7, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,145 @@ class LimitedSet { constructor(size) { this.size = size this.queue = [] this.set = new Set() } has(key) { return this.set.has(key) } add(key, value = true) { if (this.queue.length === this.size) { const last = this.queue.shift() this.set.delete(last) } this.queue.push(key) this.set.add(key) } } const createKeyMap = function (posts, selector) { const keyMap = new Map() while (posts.length) { const post = posts.shift() const key = selector(post) if (!keyMap.has(key)) { keyMap.set(key, []) } const list = keyMap.get(key) list.push(post) } return keyMap } /* * Shuffle strategy * * Store the elements in a key map grouped by the selector key * Iterate the key map indefinitely in ranges of size equal to the separation * threshold. Add the first element of each key to the result. If the key is * in the limited set, this key is not considered: * * keyMap * ------------------ | keyMap: * A B C D E F | A: [1, 2, 4], * ----------------- | B: [3, 5], * 1 3 6 7 8 10 | C: [6], * 2 5 - 9 - - | D: [7, 9] * 4 - - - - - | E: [8], * F: [10] * * Keys: A, B, C, D, E, F * Separation THR: 4 * Expected Result: 1, 3, 6, 7, 8, 2, 5, 10, 9, 4 * * Iteration: 1 2 3 4 5 * Key: A B C D E * Limited Set: (A) (A, B) (A, B, C) (A, B, C, D) (B, C, D, E) * Result: [1] [1, 3] [1, 3, 6] [1, 3, 6, 7] [1, 3, 6, 7, 8] * * ^ ^ * Keys C and E are removed here since they ran out of elements * * * Iteration: 6 7 8 9 * Key: A B D F * Limited Set: (C, D, E, A) (D, E, A, B) (D, E, A, B) (E, A, B, F) * Result: [1, 3, 6, 7, 8, 2] [1, 3, 6, 7, 8, 2, 5] [1, 3, 6, 7, 8, 2, 5] [1, 3, 6, 7, 8, 2, 5, 10] * * ^ * Since keys A and D are in cache its element are ommited * v * * Iteration: 10 11 * Key: A D * Limited Set: (E, A, B, F) (A, B, F, D) * Result: [1, 3, 6, 7, 8, 2, 5, 10] [1, 3, 6, 7, 8, 2, 5, 10, 9] * * * Remaining elements doesn't guarantee a separation of size of the threshold * so elements are interspersed from each key */ const shuffle = function (elements = [], selector = (x => x), separationThreshold = 10) { const result = [] const limitedSet = new LimitedSet(separationThreshold) const keyMap = createKeyMap(elements, selector) let shuffledElements = null while (shuffledElements != result.length) { const generator = keyMap.keys() let index = 0 let next = generator.next() shuffledElements = result.length while (index <= separationThreshold && !next.done) { const key = next.value if (!limitedSet.has(key)) { const list = keyMap.get(key) if (list.length) { result.push(list.shift()) limitedSet.add(key) if (list.length === 0) { keyMap.delete(key) } } index++ } next = generator.next() } } // If the key map is not empty means that is impossible to guarantee a // separation of the given threshold while (keyMap.size) { const generator = keyMap.keys() let next = generator.next() while (!next.done) { const key = next.value const list = keyMap.get(key) result.push(list.shift()) if (list.length === 0) { keyMap.delete(key) } next = generator.next() } } return result }