Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
2 changed files
with
102 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright 2020 Las Venturas Playground. All rights reserved. | ||
// Use of this source code is governed by the MIT license, a copy of which can | ||
// be found in the LICENSE file. | ||
|
||
import { random } from 'base/random.js'; | ||
|
||
// Shuffles the given |value| with the Fisher-Yates algorithm. Any iterable value is supported, | ||
// although recomposition is limited to arrays (default), strings, Set and Map. The given |value| | ||
// will not be modified, although each of its values will not be cloned. | ||
// | ||
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle | ||
export function shuffle(value) { | ||
if (!isIterable(value)) | ||
throw new Error('Only iterable values can be shuffled.'); | ||
|
||
const values = [ ...value ]; | ||
let counter = values.length; | ||
|
||
while (counter > 0) { | ||
const index = random(counter--); | ||
const temp = values[counter]; | ||
|
||
values[counter] = values[index]; | ||
values[index] = temp; | ||
} | ||
|
||
// Specializations for returning shuffled data in particular data types. | ||
switch (Object.prototype.toString.call(value)) { | ||
case '[object Map]': | ||
return new Map(values); | ||
|
||
case '[object Set]': | ||
return new Set(values); | ||
|
||
case '[object String]': | ||
return values.join(''); | ||
} | ||
|
||
// Default to returning the |values| as an array. | ||
return values; | ||
} | ||
|
||
// Returns whether the given |value| is iterable. | ||
function isIterable(value) { | ||
return value !== null && typeof value[Symbol.iterator] === 'function'; | ||
} |
This file contains 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 characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright 2020 Las Venturas Playground. All rights reserved. | ||
// Use of this source code is governed by the MIT license, a copy of which can | ||
// be found in the LICENSE file. | ||
|
||
import { shuffle } from 'base/shuffle.js'; | ||
|
||
describe('shuffle', it => { | ||
it('should be able to shuffle iterables', assert => { | ||
assert.throws(() => shuffle()); | ||
assert.throws(() => shuffle(3.14)); | ||
assert.throws(() => shuffle({ yo: 1 })); | ||
assert.throws(() => shuffle(null)); | ||
|
||
// Arrays | ||
const array = shuffle([ 1, 2, 3, 4, 5 ]); | ||
|
||
assert.isTrue(Array.isArray(array)); | ||
assert.equal(array.length, 5); | ||
assert.deepEqual(array.sort(), [ 1, 2, 3, 4, 5 ]); | ||
|
||
// Maps | ||
const map = shuffle(new Map([ [ 'a', 1 ], [ 'c', 3 ], [ 'b', 2 ] ])); | ||
const sortedMap = [ ...map ].sort((lhs, rhs) => lhs[1] > rhs[1] ? 1 : -1); | ||
|
||
assert.instanceOf(map, Map); | ||
assert.equal(map.size, 3); | ||
|
||
assert.deepEqual(sortedMap, [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]); | ||
|
||
// Sets | ||
const set = shuffle(new Set([ 'aap', 'noot', 'mies' ])); | ||
|
||
assert.instanceOf(set, Set); | ||
assert.equal(set.size, 3); | ||
assert.deepEqual([ ...set ].sort(), [ 'aap', 'mies', 'noot' ]); | ||
|
||
// Strings | ||
const string = shuffle('banana'); | ||
|
||
assert.typeOf(string, 'string'); | ||
assert.equal(string.length, 6); | ||
assert.deepEqual([ ...'banana' ].sort(), [ ...string ].sort()); | ||
|
||
// Distribution | ||
// | ||
// The letters in the string 'banana' can be arranged in 60 different ways, because both the | ||
// 'A' and 'B' occur multiple times. Given 1000 attempts, we should see at least 50 of those | ||
// ways, or there's something seriously wrong with the PRNG. | ||
const distribution = new Set(); | ||
|
||
for (let iteration = 0; iteration < 1000; ++iteration) | ||
distribution.add(shuffle('banana')); | ||
|
||
assert.isAboveOrEqual(distribution.size, 50); | ||
}); | ||
}); |