Pandemonium
Pandemonium is a dead simple JavaScript/TypeScript library providing typical random-related functions such as choice
, sample
etc.
The library also provides a way to create any of the available functions using a custom random source (seedrandom, for instance).
Installation
npm install --save pandemonium
Usage
Summary
Typical helpers
Sampling
n
being the number of items in the sampled sequence and k
being the number of items to be sampled.
choice
Function returning a random item from the given array.
import choice from 'pandemonium/choice';
import {choice} from 'pandemonium';
choice(['apple', 'orange', 'pear']);
>>> 'orange'
import {createChoice} from 'pandemonium/choice';
const customChoice = createChoice(rng);
random
Function returning a random integer between given a
& b
.
import random from 'pandemonium/random';
import {random} from 'pandemonium';
random(3, 7);
>>> 4
import {createRandom} from 'pandemonium/random';
const customRandom = createRandom(rng);
randomBoolean
Function returning a random boolean.
import randomBoolean from 'pandemonium/random-boolean';
import {randomBoolean} from 'pandemonium';
randomBoolean();
>>> true
import {createrandomBoolean} from 'pandemonium/random-boolean';
const customRandomBoolean = createRandomBoolean(rng);
randomFloat
Function returning a random float between given a
& b
.
import randomFloat from 'pandemonium/random-float';
import {randomFloat} from 'pandemonium';
randomFloat(-5, 55);
>>> 6.756482
import {createRandomFloat} from 'pandemonium/random-float';
const customRandomFloat = createRandomFloat(rng);
randomIndex
Function returning a random index of the given array.
import randomIndex from 'pandemonium/random-index';
import {randomIndex} from 'pandemonium';
randomIndex(['apple', 'orange', 'pear']);
>>> 1
randomIndex(3);
>>> 2
import {createRandomIndex} from 'pandemonium/random-index';
const customRandomIndex = createRandomIndex(rng);
randomString
Function returning a random string.
import randomString from 'pandemonium/random-string';
import {randomString} from 'pandemonium';
randomString(5);
>>> 'gHepM'
randomString(3, 7);
>>> 'hySf3'
import {createRandomString} from 'pandemonium/random-string';
const customRandomString = createRandomString(rng);
const customRandomString = createRandomString(rng, 'ATGC');
randomUint32
Function returning a random unsigned 32bits number.
import {randomUint32} from 'pandemonium/random-typed-int';
import {randomUint32} from 'pandemonium';
randomUint32();
>>> 397536
import {createRandomUint32} from 'pandemonium/random-typed-int';
const customRandomUint32 = createRandomUint32(rng);
randomPair
Function returning a random pair from the given array.
Note that this function will return unordered pairs (i.e. [0, 1]
and [1, 0]
are to be considered the same) and will not return pairs containing twice the same item (i.e. [0, 0]
).
import randomPair from 'pandemonium/random-pair';
import {randomPair} from 'pandemonium';
randomPair(['apple', 'orange', 'pear', 'cherry']);
>>> ['orange', 'cherry']
randomPair(4);
>>> [1, 3]
import {createRandomPair} from 'pandemonium/random-pair';
const customRandomPair = createRandomPair(rng);
randomOrderedPair
Function returning a random ordered pair (i.e. [0, 1]
won't be considered to be the same as [1, 0]
) from the given array.
import randomOrderedPair from 'pandemonium/random-ordered-pair';
import {randomOrderedPair} from 'pandemonium';
randomOrderedPair(['apple', 'orange', 'pear', 'cherry']);
>>> ['cherry', 'apple']
randomOrderedPair(4);
>>> [3, 0]
import {createRandomOrderedPair} from 'pandemonium/random-ordered-pair';
const customRandomPair = createRandomOrderedPair(rng);
shuffle
Function returning a shuffled version of the given array using the Fisher-Yates algorithm.
If what you need is to shuffle the original array in place, check out shuffleInPlace
.
import shuffle from 'pandemonium/shuffle';
import {shuffle} from 'pandemonium';
shuffle(['apple', 'orange', 'pear', 'pineapple']);
>>> ['pear', 'orange', 'apple', 'pineapple']
import {createShuffle} from 'pandemonium/shuffle';
const customShuffle = createShuffle(rng);
shuffleInPlace
Function shuffling the given array in place using the Fisher-Yates algorithm.
import shuffleInPlace from 'pandemonium/shuffle-in-place';
import {shuffleInPlace} from 'pandemonium';
const array = ['apple', 'orange', 'pear', 'pineapple'];
shuffleInPlace(array);
array >>> ['pear', 'orange', 'apple', 'pineapple'];
import {createShuffleInPlace} from 'pandemonium/shuffle-in-place';
const customShuffleInPlace = createShuffleInPlace(rng);
weightedChoice
Function returning a random item from the given array of weights.
Note that weights don't need to be relative.
import weightedChoice from 'pandemonium/weighted-choice';
import {weightedChoice} from 'pandemonium';
const array = [.1, .1, .4, .3, .1];
weightedChoice(array);
>>> .4
import {createWeightedChoice} from 'pandemonium/weighted-choice';
const customWeightedChoice = createWeightedChoice(rng);
const customWeightedChoice = createWeightedChoice({
rng: rng,
getWeight: (item, index) => {
return item.weight;
}
});
const array = [{fruit: 'pear', weight: 4}, {fruit: 'apple', weight: 30}];
customWeightedChoice(array);
>>> 'apple'
import {createCachedWeightedChoice} from 'pandemonium/weighted-choice';
const array = [.1, .1, .4, .3, .1];
const customWeightedChoice = createCachedWeightedChoice(rng, array);
customWeightedChoice();
>>> .3
weightedRandomIndex
Function returning a random index from the given array of weights.
Note that weights don't need to be relative.
import weightedRandomIndex from 'pandemonium/weighted-random-index';
import {weightedRandomIndex} from 'pandemonium';
const array = [.1, .1, .4, .3, .1];
weightedRandomIndex(array);
>>> 2
import {createWeightedRandomIndex} from 'pandemonium/weighted-random-index';
const customWeightedRandomIndex = createWeightedRandomIndex(rng);
const customWeightedRandomIndex = createWeightedRandomIndex({
rng: rng,
getWeight: (item, index) => {
return item.weight;
}
});
const array = [{fruit: 'pear', weight: 4}, {fruit: 'apple', weight: 30}];
customWeightedRandomIndex(array);
>>> 1
import {createCachedWeightedRandomIndex} from 'pandemonium/weighted-random-index';
const array = [.1, .1, .4, .3, .1];
const customWeightedRandomIndex = createCachedWeightedRandomIndex(rng, array);
customWeightedRandomIndex();
>>> 3
dangerouslyMutatingSample
Function returning a random sample of size k
from the given array.
This function runs in O(k)
time & memory but is somewhat dangerous because it will mutate the given array while performing its Fisher-Yates shuffle before reverting the mutations at the end.
import dangerouslyMutatingSample from 'pandemonium/dangerously-mutating-sample';
import {dangerouslyMutatingSample} from 'pandemonium';
dangerouslyMutatingSample(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear']
import {createDangerouslyMutatingSample} from 'pandemonium/dangerously-mutating-sample';
const customSample = createDangerouslyMutatingSample(rng);
fisherYatesSample
Function returning a random sample of size k
from the given array.
This function uses a partial Fisher-Yates shuffle and therefore runs in O(k)
time but must clone the given array to work, which adds O(n)
time & memory.
import fisherYatesSample from 'pandemonium/fisher-yates-sample';
import {fisherYatesSample} from 'pandemonium';
fisherYatesSample(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear']
import {createFisherYatesSample} from 'pandemonium/fisherYatesSample';
const customFisherYatesSample = createFisherYatesSample(rng);
geometricReservoirSample
Function returning a random sample of size k
from the given array.
This function runs in O(k * (1 + log(n / k)))
time & O(k)
memory using "Algorithm L" taken from the following paper:
Li, Kim-Hung. "Reservoir-sampling algorithms of time complexity O(n (1+ log (N/n)))." ACM Transactions on Mathematical Software (TOMS) 20.4 (1994): 481-493.
Note that this function is able to sample indices without requiring you to represent the range of indices in memory.
import geometricReservoirSample from 'pandemonium/geometric-reservoir-sample';
import {geometricReservoirSample} from 'pandemonium';
geometricReservoirSample(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear']
geometricReservoirSample(2, 4);
>>> [0, 2]
import {createGeometricReservoirSample} from 'pandemonium/geometric-reservoir-sample';
const customSample = createGeometricReservoirSample(rng);
naiveSample
Function returning a random sample of size k
from the given array.
This function works by keeping a Set
of the already picked items and choosing a random item in the array until we have the desired k
items.
While it is a good pick for cases when k
is little compared to the size of your array, this function will see its performance drop really fast when k
becomes proportionally bigger.
Note that this function is able to sample indices without requiring you to represent the range of indices in memory.
import naiveSample from 'pandemonium/naive-sample';
import {naiveSample} from 'pandemonium';
naiveSample(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear']
naiveSample(2, 4);
>>> [0, 2]
import {createNaiveSample} from 'pandemonium/naive-sample';
const customSample = createNaiveSample(rng);
reservoirSample
Function returning a random sample of size k
from the given array.
This function runs in O(n)
time and O(k)
memory.
A helper class able to work on an arbitrary stream of data that does not need to fit into memory is also available if you need it.
import reservoirSample from 'pandemonium/reservoir-sample';
import {reservoirSample} from 'pandemonium';
reservoirSample(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear']
import {createReservoirSample} from 'pandemonium/reservoir-sample';
const customReservoirSample = createReservoirSample(rng);
import {ReservoirSampler} from 'pandemonium/reservoir-sample';
const sampler = new ReservoirSampler(10, rng);
for (const value of lazyIterable) {
sampler.process(value);
}
const sample = sampler.end();
sampleWithReplacements
Function returning a random sample of size k
with replacements from the given array. This prosaically means that an items from the array might occur several times in the resulting sample.
The function runs in both O(k)
time & space complexity.
import sampleWithReplacements from 'pandemonium/sample-with-replacements';
import {sampleWithReplacements} from 'pandemonium';
sampleWithReplacements(3, ['apple', 'orange', 'pear', 'pineapple']);
>>> ['apple', 'pear', 'apple']
import {createSampleWithReplacements} from 'pandemonium/sample-with-replacements';
const customSample = createSampleWithReplacements(rng);
samplePairs
Function returning a random sample of k
unique unordered pairs from the given array.
It works by storing a unique key created from the picked pairs, making it a specialized variant of naiveSample.
It is usually quite efficient because when sampling pairs, the total size of the population, being combinatorial, is often magnitudes larger than the size of the sample we need to retrieve.
Note finally that this function is able to sample pairs of indices without requiring you to represent the range of indices in memory.
import samplePairs from 'pandemonium/sample-pairs';
import {samplePairs} from 'pandemonium';
samplePairs(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> [['apple', 'pear'], ['orange', 'pear']]
samplePairs(2, 4);
>>> [[0, 2], [1, 2]]
import {createSamplePairs} from 'pandemonium/sample-pairs';
const customSamplePairs = createSamplePairs(rng);
sampleOrderedPairs
Function returning a random sample of k
unique ordered pairs from the given array.
It works by storing a unique key created from the picked pairs, making it a specialized variant of naiveSample.
It is usually quite efficient because when sampling pairs, the total size of the population, being combinatorial, is often magnitudes larger than the size of the sample we need to retrieve.
Note finally that this function is able to sample pairs of indices without requiring you to represent the range of indices in memory.
import sampleOrderedPairs from 'pandemonium/sample-pairs';
import {sampleOrderedPairs} from 'pandemonium';
sampleOrderedPairs(2, ['apple', 'orange', 'pear', 'pineapple']);
>>> [['apple', 'pear'], ['pear', 'orange']]
sampleOrderedPairs(2, 4);
>>> [[0, 2], [2, 1]]
import {createSampleOrderedPairs} from 'pandemonium/sample-ordered-pairs';
const customSampleOrderedPairs = createSampleOrderedPairs(rng);
weightedReservoirSample
Function returning a random sample of size k
from a given array of weighted items.
The result is a sample without replacement, which, in the case of weighted items, has been chosen to mean that subsequent items are picked based on the proportional total weight of the remaining items.
We use algorithm "A-ES" from the following papers:
Pavlos S. Efraimidis, Paul G. Spirakis. "Weighted random sampling with a reservoir." https://arxiv.org/pdf/1012.0256.pdf
Pavlos S. Efraimidis. "Weighted Random Sampling over Data Streams."
This function runs in O(n)
time and O(k)
memory.
A helper class working able to work on an arbitrary stream of data that does not need to fit into memory is also available if you need it.
import weightedReservoirSample from 'pandemonium/weighted-reservoir-sample';
import {weightedReservoirSample} from 'pandemonium';
weightedReservoirSample(2, [.1, .1, .4, .3, .05]);
>>> [.3, .4]
import {createWeightedReservoirSample} from 'pandemonium/weighted-reservoir-sample';
const customWeightedReservoirSample = createWeightedReservoirSample(rng);
const data = [{label: 'orange', importance: 34}, ...];
const customWeightedReservoirSample = createWeightedReservoirSample({
getWeight: item => item.importance
});
import {WeightedReservoirSampler} from 'pandemonium/weighted-reservoir-sample';
const sampler = new WeightedReservoirSampler(10, {rng, getWeight});
for (const value of lazyIterable) {
sampler.process(value);
}
const sample = sampler.end();
Contribution
Contributions are obviously welcome. Please be sure to lint the code & add the relevant unit tests before submitting any PR.
git clone git@github.com:Yomguithereal/pandemonium.git
cd pandemonium
npm install
# To lint the code
npm run lint
# To run the unit tests
npm test
License
MIT