UNPKG

random-seed-weighted-chooser

Version:

A random weighted item chooser with custom seed option for JavaScript and TypeScript.

112 lines (111 loc) 4.95 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * This PRNG allows seeding, unlike the lame `Math.random()`. * Use it like so: ```js let seededRandFunc = new seedrandom(seed); seededRandFunc(); // Number from 0 to 1 ``` */ const seedrandom_1 = __importDefault(require("seedrandom")); /** * Contains static weighted chooser functions. */ class Chooser { } /** * Choose an index based on the weights provided in the number array. Higher weights increase likeliness of being chosen. * Returns the chosen index, or `-1` if the array was empty or all weights were `0`. * * Only the first argument is required. * * Allows an optional random seed and default weight to use if array values are not numbers. * * All negative weights are converted to their absolute value. * * @param weights Weights as an array of numbers. * @param seed Optional. Seed used for pseudorandom number generator (PRNG). Defaults to `Math.random()`. * @param defaultWeight Optional. Default weight to use if one of the values is not a number. Defaults to `1`. * * @returns The chosen index as a number, or `-1` if the array was empty or all weights were `0`. */ Chooser.chooseWeightedIndex = (weights, seed = Math.random(), defaultWeight = 1) => { // If the array is falsy, not an array, or empty, return -1. if (!weights || !Array.isArray(weights) || weights.length <= 0) { return -1; } // Keep it positive. defaultWeight = Math.abs(defaultWeight); let cumulative = 0; // Add all weights to cumulative, and build an array of each cumulative value. // For example, if the weights are [5, 30, 10], this would build an array // containing [5, 35, 45], and cumulative=45. const ranges = weights.map((weight) => (cumulative += typeof weight === 'number' && weight >= 0 ? Math.abs(weight) : defaultWeight)); // Get our PRNG function using the seed. const seededRandFunc = new seedrandom_1.default(seed); // Select our value. const selectedValue = seededRandFunc() * cumulative; // If the selected value is within one of the ranges, that's our choice! for (let index = 0; index < ranges.length; index++) { if (selectedValue < ranges[index]) { return index; } } // If nothing was chosen, all weights were 0 or something went wrong. return -1; }; /** * Choose an object based on the `"weight"` properties in the object within the provided array. * Higher weights increase likeliness of being chosen. * Returns the chosen object, or `null` if the array was empty or all weights were `0`. * * Only the first argument is required. * * Optionally, you can specify a weight property key, a default weight to use if weight values are not numbers, and random seed. * * All negative weights are converted to their absolute value. * * @param arrayOfObjects An array of objects to choose from. Each item should have a weight property. * @param weightPropertyKey Optional. The weight property key to use on each object. Defaults to `"weight"`. * @param defaultWeight Optional. Default weight to use if one of the values is not a number. Defaults to `1`. * @param seed Optional. Seed used for pseudorandom number generator (PRNG). Defaults to `Math.random()`. * * @returns The chosen object, or `null` if the array was empty or all weights were `0`. */ Chooser.chooseWeightedObject = (arrayOfObjects, weightPropertyKey = 'weight', defaultWeight = 1, seed = Math.random()) => { // If the array is falsy, not an array, or empty, return null. if (!arrayOfObjects || !Array.isArray(arrayOfObjects) || arrayOfObjects.length <= 0) { return null; } // Drop the sign. https://www.youtube.com/watch?v=I7Hea6tdg0c defaultWeight = Math.abs(defaultWeight); // Collect the weights from the objects into an array. const weights = arrayOfObjects.map((currItem) => { // When in doubt, we'll use the default. let currWeight = defaultWeight; // We expect each item to have the weight property if (!!currItem) { const propValue = currItem[weightPropertyKey]; // Use the abs of the prop value, but only if it's a number. if (typeof propValue === 'number') { currWeight = Math.abs(propValue); } } return currWeight; }); // Choose an index based on the weights... const chosenIndex = Chooser.chooseWeightedIndex(weights, seed, defaultWeight); // If an index was chosen, return the object for that index. if (chosenIndex >= 0) { return arrayOfObjects[chosenIndex]; } else { // Otherwise all weights were 0 or something went wrong. Return null. return null; } }; exports.default = Chooser;