From 0dd55b3f1915d368936b3eed276db2be360f8bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Tue, 6 Apr 2021 18:36:38 +0200 Subject: [PATCH 1/5] :books: docs(_fisheryates): Add important note about the definition of k. --- src/kernel/_fisheryates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/kernel/_fisheryates.js b/src/kernel/_fisheryates.js index 9f58486..3a322d4 100644 --- a/src/kernel/_fisheryates.js +++ b/src/kernel/_fisheryates.js @@ -4,7 +4,9 @@ const _fisheryates = (randint) => (n, a, i, j) => { // We will swap at most n elements - + // NOTE: When n = j - i, the last swap swaps a[j-1] with itself, + // which is a NOOP. /!\ HOWEVER, the last swap is NOT a NOOP when + // n < j - i. Hence we cannot let k = i + n - 1 in general. const k = i + n; for (; i < k; ++i) { From 21c8bdd09fccb513adef8bf9b653d818e792ac85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Tue, 6 Apr 2021 18:50:59 +0200 Subject: [PATCH 2/5] :books: docs(_fisheryates): Add references. --- src/kernel/_fisheryates.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/kernel/_fisheryates.js b/src/kernel/_fisheryates.js index 3a322d4..ea3869e 100644 --- a/src/kernel/_fisheryates.js +++ b/src/kernel/_fisheryates.js @@ -1,7 +1,24 @@ /** - * Sample array using Fisher-Yates method. + * Sample element from an array using Fisher-Yates method. + * + * NOTE: The original description of the algorithm by Fisher and Yates [1] had + * unnecessary bookkeeping which made the algorithm run in O(n * (j-i)) time. + * This implementation follows Durstenfeld's [2] and Knuth's [3] descriptions + * which yield O(n) running time. + * + * For more information see the excellent "Fisher–Yates shuffle" page on + * Wikipedia [4]. Fisher and Yates description is referred there as + * "Fisher and Yate's original method" or "pencil-and-paper method". + * The more efficient implementation described by Durstenfeld and Knuth is + * referred there as "the modern method". + * + * [1] Fisher, Ronald A.; Yates, Frank (1938). Statistical tables for + * biological, agricultural and medical research. + * [2] Durstenfeld, R. (July 1964). "Algorithm 235: Random permutation" + * [3] Knuth, Donald E. (1969). Seminumerical algorithms. The Art of Computer + * Programming Volume 2. + * [4] https://en.wikipedia.org/wiki/Fisher–Yates_shuffle */ - const _fisheryates = (randint) => (n, a, i, j) => { // We will swap at most n elements // NOTE: When n = j - i, the last swap swaps a[j-1] with itself, From 49b7b197c3288d79ad7aedf1a2e3e9978d657c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Tue, 6 Apr 2021 19:06:10 +0200 Subject: [PATCH 3/5] :books: docs(sample/_fisheryates): Document function signatures. --- src/api/sample.js | 11 ++++++++++ src/kernel/_fisheryates.js | 45 ++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/api/sample.js b/src/api/sample.js index 764a5aa..2a59cdc 100644 --- a/src/api/sample.js +++ b/src/api/sample.js @@ -1,5 +1,16 @@ import _fisheryates from '../kernel/_fisheryates.js'; import randint from './randint.js'; +/** + * Take a sample of size n (without repetitions) from the items i through + * j-1 of the input array. This is done in-place. The sample can be retrieved + * from position i to i+n. + * + * @function + * @param {number} n The size of the sample. + * @param {Array} a The input array. + * @param {number} i The inclusive left bound. + * @param {number} j The non-inclusive right bound. + */ const sample = _fisheryates(randint); export default sample; diff --git a/src/kernel/_fisheryates.js b/src/kernel/_fisheryates.js index ea3869e..b981728 100644 --- a/src/kernel/_fisheryates.js +++ b/src/kernel/_fisheryates.js @@ -18,25 +18,42 @@ * [3] Knuth, Donald E. (1969). Seminumerical algorithms. The Art of Computer * Programming Volume 2. * [4] https://en.wikipedia.org/wiki/Fisher–Yates_shuffle + * + * @param {Function} randint The randint function. + * @return {Function} The sampling function. */ -const _fisheryates = (randint) => (n, a, i, j) => { - // We will swap at most n elements - // NOTE: When n = j - i, the last swap swaps a[j-1] with itself, - // which is a NOOP. /!\ HOWEVER, the last swap is NOT a NOOP when - // n < j - i. Hence we cannot let k = i + n - 1 in general. - const k = i + n; +const _fisheryates = (randint) => { + /** + * Take a sample of size n (without repetitions) from the items i through + * j-1 of the input array. This is done in-place. The sample can be + * retrieved from position i to i+n. + * + * @param {number} n The size of the sample. + * @param {Array} a The input array. + * @param {number} i The inclusive left bound. + * @param {number} j The non-inclusive right bound. + */ + const sample = (n, a, i, j) => { + // We will swap at most n elements + // NOTE: When n = j - i, the last swap swaps a[j-1] with itself, + // which is a NOOP. /!\ HOWEVER, the last swap is NOT a NOOP when + // n < j - i. Hence we cannot let k = i + n - 1 in general. + const k = i + n; + + for (; i < k; ++i) { + // Choose any index p in the remaining array - for (; i < k; ++i) { - // Choose any index p in the remaining array + const p = randint(i, j); - const p = randint(i, j); + // Swap element at index p with first element in the array - // Swap element at index p with first element in the array + const tmp = a[i]; + a[i] = a[p]; + a[p] = tmp; + } + }; - const tmp = a[i]; - a[i] = a[p]; - a[p] = tmp; - } + return sample; }; export default _fisheryates; From 686690dc08ac2aa04c0290d26c0065754c6c927e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Tue, 6 Apr 2021 19:06:34 +0200 Subject: [PATCH 4/5] :books: docs(shuffle/_shuffle): Document function signatures. --- src/api/shuffle.js | 8 ++++++++ src/kernel/_shuffle.js | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/api/shuffle.js b/src/api/shuffle.js index fba3121..6ef786b 100644 --- a/src/api/shuffle.js +++ b/src/api/shuffle.js @@ -1,5 +1,13 @@ import _shuffle from '../kernel/_shuffle.js'; import sample from './sample.js'; +/** + * Shuffle array in-place between positions i and j-1 (inclusive). + * The other positions are left untouched. + * @function + * @param {Array} a The input array. + * @param {number} i The inclusive left bound. + * @param {number} j The non-inclusive right bound. + */ const shuffle = _shuffle(sample); export default shuffle; diff --git a/src/kernel/_shuffle.js b/src/kernel/_shuffle.js index 121a06c..3921806 100644 --- a/src/kernel/_shuffle.js +++ b/src/kernel/_shuffle.js @@ -1,6 +1,20 @@ /** - * Shuffle array by sampling the complete array. + * Shuffle the array by sampling the entire array. + * + * @param {Function} sample The sample function. + * @return {Function} The shuffle function. */ +const _shuffle = (sample) => { + /** + * Shuffle array in-place between positions i and j-1 (inclusive). + * The other positions are left untouched. + * + * @param {Array} a The input array. + * @param {number} i The inclusive left bound. + * @param {number} j The non-inclusive right bound. + */ + const shuffle = (a, i, j) => sample(j - i, a, i, j); + return shuffle; +}; -const _shuffle = (sample) => (a, i, j) => sample(j - i, a, i, j); export default _shuffle; From 8b189751eca6d58045202ccfde9cda5cc3fdb08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Tue, 6 Apr 2021 19:07:17 +0200 Subject: [PATCH 5/5] :hatching_chick: release: Bumping to v3.2.1. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9971ed6..be3fe94 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@aureooms/js-random", "description": "Randomness algorithms for JavaScript", - "version": "3.2.0", + "version": "3.2.1", "license": "AGPL-3.0", "author": "Aurélien Ooms ", "homepage": "https://aureooms.github.io/js-random",