|
| 1 | +/** |
| 2 | + * Shuffle elements of an iterable using an inside-out implementation of the |
| 3 | + * Fisher-Yates method. |
| 4 | + * |
| 5 | + * One can observe that if the input contains n elements, the loop has exactly |
| 6 | + * n! possible outcomes: one for the first iteration, two for the second, three |
| 7 | + * for the third, etc., the number of outcomes of a loop being the product of |
| 8 | + * the number of outcomes for each iteration. Given a perfect randint function, |
| 9 | + * each iteration's outcomes are equally likely, and independent of other |
| 10 | + * iterations outcomes. The proof below shows that these outcomes are |
| 11 | + * distinct. |
| 12 | + * |
| 13 | + * To see that this method yields the correct result (assume perfect randint): |
| 14 | + * 1. Observe that it is correct when the input is empty. |
| 15 | + * 2. By induction: |
| 16 | + * - Induction hypothesis: assume it is correct when the input consists of |
| 17 | + * n elements. |
| 18 | + * - We almost insert the (n+1)th element at one of the n+1 possible |
| 19 | + * insertion position in the output array. Almost because we move the |
| 20 | + * element that is at the insertion position at the end instead of |
| 21 | + * shifting the elements right of the insertion position to make room for |
| 22 | + * the inserted element. |
| 23 | + * - Ideally, since we inserted the last element at one of the n+1 |
| 24 | + * positions, we would like that the elements inserted earlier form one |
| 25 | + * of n! permutations uniformly at random after moving the element under |
| 26 | + * the insertion position. This is true because the permutations that we |
| 27 | + * obtain after this move are in one-to-one correspondance with the n! |
| 28 | + * distinct permutations that can be obtained before the move. These are |
| 29 | + * equally likely to be produced by the induction hypothesis. |
| 30 | + * |
| 31 | + * @param {Function} randint The randint function. |
| 32 | + * @return {Function} The sampling function. |
| 33 | + */ |
| 34 | +const _fisheryates_inside_out = (randint) => { |
| 35 | + /** |
| 36 | + * Given an input iterable, constructs an array containing the elements of |
| 37 | + * the input shuffled uniformly at random. |
| 38 | + * |
| 39 | + * @param {Iterable} iterable The input iterable. |
| 40 | + * @param {Array} [output=[]] The constructed array. |
| 41 | + * @return {Array} The constructed array. |
| 42 | + */ |
| 43 | + const shuffled = (iterable, output = []) => { |
| 44 | + let n = 0; |
| 45 | + for (const item of iterable) { |
| 46 | + const i = randint(-1, n); |
| 47 | + if (i === -1) output.push(item); |
| 48 | + else { |
| 49 | + output.push(output[i]); |
| 50 | + output[i] = item; |
| 51 | + } |
| 52 | + |
| 53 | + ++n; |
| 54 | + } |
| 55 | + |
| 56 | + return output; |
| 57 | + }; |
| 58 | + |
| 59 | + return shuffled; |
| 60 | +}; |
| 61 | + |
| 62 | +export default _fisheryates_inside_out; |
0 commit comments