random-pie
Version:
A lightweight TypeScript/JavaScript library providing Python-style random number generation and randomization utilities. This utility module implements the most common functions from Python's random module, making it intuitive for Python developers workin
176 lines (175 loc) • 6.64 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.choice = choice;
exports.choices = choices;
exports.shuffle = shuffle;
exports.shuffled = shuffled;
exports.sample = sample;
const utils_1 = require("./utils");
/**
* Return a random element from the given array.
*
* @param {Array} arr The input array
* @throws TypeError if the given argument is not an array
* @throws RangeError if the given array is empty
* @returns {*} An element from the given array
*/
function choice(arr) {
if (!Array.isArray(arr)) {
throw new TypeError("Argument must be an array");
}
if (arr.length === 0) {
throw new RangeError("Input array cannot be empty");
}
return arr[Math.floor(Math.random() * arr.length)];
}
/**
* Return a list of randomly selected elements from the given array.
*
* @param {Array} arr The input array
* @param {Object} [options] Configuration options for selection
* @param {Array} [options.weights=null] An array of weights corresponding to each element in arr
* @param {Array} [options.cumWeights=null] An array of cumulative weights
* @param {number} [options.k=1] The number of elements to select
* @throws TypeError if the first argument is not an array
* @throws RangeError if the array is empty
* @throws TypeError if k is not a non-negative integer
* @throws TypeError if weights or cumWeights are not arrays
* @throws RangeError if weights or cumWeights length does not match array length
* @throws RangeError if weights contain negative values
* @throws RangeError if weights contain only zeros
* @returns {Array} An array containing k randomly selected elements
*/
function choices(arr, { weights = null, cumWeights = null, k = 1 } = {}) {
if (!Array.isArray(arr)) {
throw new TypeError("First argument must be an array");
}
if (arr.length === 0) {
throw new RangeError("Population cannot be empty");
}
if (!Number.isInteger(k) || k < 0) {
throw new TypeError("k must be a non-negative integer");
}
if (weights && !Array.isArray(weights)) {
throw new TypeError("weights must be an array");
}
if (weights && weights.length !== arr.length) {
throw new RangeError("weights length must match array length");
}
if (weights === null || weights === void 0 ? void 0 : weights.some((w) => w < 0)) {
throw new RangeError("weights must be non-negative");
}
else if (weights === null || weights === void 0 ? void 0 : weights.every((w) => w === 0)) {
throw new RangeError("weights must contain at least one non-zero value");
}
if (cumWeights && !Array.isArray(cumWeights)) {
throw new TypeError("cumWeights must be an array");
}
if (cumWeights && cumWeights.length !== arr.length) {
throw new RangeError("cumWeights length must match array length");
}
if (weights && cumWeights) {
throw new TypeError("Cannot specify both weights and cumWeights");
}
const totalWeights = cumWeights || (weights ? (0, utils_1.accumulate)(weights) : null);
const total = totalWeights ? totalWeights[totalWeights.length - 1] : arr.length;
const result = [];
for (let i = 0; i < k; i++) {
const rand = Math.random() * total;
if (!totalWeights) {
result.push(arr[Math.floor(rand)]);
continue;
}
let left = 0;
let right = totalWeights.length - 1;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (totalWeights[mid] <= rand) {
left = mid + 1;
}
else {
right = mid;
}
}
result.push(arr[left]);
}
return result;
}
/**
* Shuffles the given array in place.
*
* @param {Array} arr The input array
* @throws TypeError if the given argument is not an array
*/
function shuffle(arr) {
if (!Array.isArray(arr)) {
throw new TypeError("Input must be an array");
}
const length = arr.length;
for (let i = length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
/**
* Returns a shuffled copy of the given array.
*
* @param {Array} arr The input array
* @throws TypeError if the given argument is not an array
* @returns {Array} A new array with the same elements, in a random order
*/
function shuffled(arr) {
if (!Array.isArray(arr)) {
throw new TypeError("Input must be an array");
}
const arrCopy = [...arr];
shuffle(arrCopy);
return arrCopy;
}
/**
* Returns k unique elements chosen from the population sequence. If k is larger than
* the population size, raise a RangeError. If counts is given, elements from the
* population are selected according to the given weights. If the relative weights are
* not specified, the selections are made with equal probability.
*
* @param {Array} arr The population sequence
* @param {number} k The number of elements to choose
* @param {Array<number>=} counts The weights for each element in the population
* @throws TypeError if the given arguments are invalid
* @throws RangeError if k is larger than the population size
* @returns {Array} A list of k unique elements from the population
*/
function sample(arr, k, counts = null) {
if (!Array.isArray(arr)) {
throw new TypeError("First argument must be an array");
}
if (!Number.isInteger(k) || k < 0) {
throw new TypeError("k must be a non-negative integer");
}
if (k > arr.length) {
throw new RangeError("k cannot be larger than array length");
}
if (counts !== null) {
if (!Array.isArray(counts)) {
throw new TypeError("counts must be an array");
}
if (counts.length !== arr.length) {
throw new RangeError("counts length must match array length");
}
if (!counts.every((n) => Number.isInteger(n) && n >= 0)) {
throw new TypeError("counts must contain non-negative integers");
}
}
const expandedArr = counts ? arr.flatMap((item, index) => Array(counts[index]).fill(item)) : arr;
if (k > expandedArr.length) {
throw new RangeError("k cannot be larger than population size");
}
const pool = [...expandedArr];
const result = [];
for (let i = 0; i < k; i++) {
const j = Math.floor(Math.random() * (pool.length - i));
[pool[j], pool[pool.length - 1 - i]] = [pool[pool.length - 1 - i], pool[j]];
result.push(pool[pool.length - 1 - i]);
}
return result;
}